/*-------------------------------------------------------------------------
 *
 * postgres_fdw.c
 *		  远程 PostgreSQL 服务器的外部数据包装器
 *
 * Portions Copyright (c) 2012-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		  contrib/postgres_fdw/postgres_fdw.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <limits.h>

#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/pg_class.h"
#include "catalog/pg_opfamily.h"
#include "commands/defrem.h"
#include "commands/explain.h"
#include "commands/vacuum.h"
#include "executor/execAsync.h"
#include "foreign/fdwapi.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/appendinfo.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/inherit.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "parser/parsetree.h"
#include "postgres_fdw.h"
#include "storage/latch.h"
#include "utils/builtins.h"
#include "utils/float.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/sampling.h"
#include "utils/selfuncs.h"

PG_MODULE_MAGIC;

/* 默认的远程查询启动 CPU 成本。 */
#define DEFAULT_FDW_STARTUP_COST	100.0

/* 处理 1 行的默认 CPU 成本（超过 cpu_tuple_cost）。 */
#define DEFAULT_FDW_TUPLE_COST		0.01

/* 如果没有远程估算，假设排序成本增加 20% */
#define DEFAULT_FDW_SORT_MULTIPLIER 1.2

/*
 * 存储在 fdw_private 列表中的 FDW 私有信息的索引。
 *
 * 这些项使用枚举 FdwScanPrivateIndex 进行索引，因此可以使用 list_nth() 获取某一项。 
 * 例如，要获取 SELECT 语句：
 *		sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
 */
enum FdwScanPrivateIndex
{
	/* 远程执行的 SQL 语句（作为字符串节点） */
	FdwScanPrivateSelectSql,
	/* 由 SELECT 检索的属性编号的整数列表 */
	FdwScanPrivateRetrievedAttrs,
	/* 表示所需 fetch_size 的整数 */
	FdwScanPrivateFetchSize,

	/*
	 * 描述连接的字符串，即被连接的关系的名称和连接类型，
	 * 当扫描是连接时添加 
	 */
	FdwScanPrivateRelations
};

/*
 * 类似地，此枚举描述在引用 postgres_fdw 外部表的 ModifyTable 节点中
 * fdw_private 列表中保存的内容。我们存储：
 *
 * 1) 要发送到远程服务器的 INSERT/UPDATE/DELETE 语句文本
 * 2) 用于 INSERT/UPDATE 的目标属性编号的整数列表
 *	  （DELETE 为 NIL）
 * 3) INSERT 的 VALUES 子句末尾的长度
 *	  （DELETE/UPDATE 为 -1）
 * 4) 布尔标志，指示远程查询是否具有 RETURNING 子句
 * 5) 由 RETURNING 检索的属性编号的整数列表（如果有的话）
 */
enum FdwModifyPrivateIndex
{
	/* 远程执行的 SQL 语句（作为字符串节点） */
	FdwModifyPrivateUpdateSql,
	/* 用于 INSERT/UPDATE 的目标属性编号的整数列表 */
	FdwModifyPrivateTargetAttnums,
	/* VALUES 子句末尾的长度（作为整数节点） */
	FdwModifyPrivateLen,
	/* has-returning 标志（作为布尔节点） */
	FdwModifyPrivateHasReturning,
	/* 由 RETURNING 检索的属性编号的整数列表 */
	FdwModifyPrivateRetrievedAttrs
};

/*
 * 类似地，此枚举描述在直接修改外部表的 ForeignScan 节点中
 * fdw_private 列表中保存的内容。我们存储：
 *
 * 1) 要发送到远程服务器的 UPDATE/DELETE 语句文本
 * 2) 布尔标志，指示远程查询是否具有 RETURNING 子句
 * 3) 由 RETURNING 检索的属性编号的整数列表（如果有的话）
 * 4) 布尔标志，指示我们是否设置了命令 es_processed
 */
enum FdwDirectModifyPrivateIndex
{
	/* 远程执行的 SQL 语句（作为字符串节点） */
	FdwDirectModifyPrivateUpdateSql,
	/* has-returning 标志（作为布尔节点） */
	FdwDirectModifyPrivateHasReturning,
	/* 由 RETURNING 检索的属性编号的整数列表 */
	FdwDirectModifyPrivateRetrievedAttrs,
	/* set-processed 标志（作为布尔节点） */
	FdwDirectModifyPrivateSetProcessed
};

/*
 * 使用 postgres_fdw 的外部扫描的执行状态。
 */
typedef struct PgFdwScanState
{
	Relation	rel;			/* 外部表的 relcache 条目。 NULL
								 * 用于外部连接扫描。 */
	TupleDesc	tupdesc;		/* 扫描的元组描述符 */
	AttInMetadata *attinmeta;	/* 属性数据类型转换元数据 */

	/* 提取的 fdw_private 数据 */
	char	   *query;			/* SELECT 命令的文本 */
	List	   *retrieved_attrs;	/* 检索的属性编号列表 */

	/* 用于远程查询执行 */
	PGconn	   *conn;			/* 扫描的连接 */
	PgFdwConnState *conn_state; /* 每连接的额外状态 */
	unsigned int cursor_number; /* 我的游标的准唯一 ID */
	bool		cursor_exists;	/* 我们是否创建了游标？ */
	int			numParams;		/* 传递给查询的参数数量 */
	FmgrInfo   *param_flinfo;	/* 对它们的输出转换函数 */
	List	   *param_exprs;	/* 参数值的可执行表达式 */
	const char **param_values;	/* 查询参数的文本值 */

	/* 用于存储结果元组 */
	HeapTuple  *tuples;			/* 当前检索到的元组数组 */
	int			num_tuples;		/* 数组中的元组数量 */
	int			next_tuple;		/* 下一个要返回的索引 */

	/* 批处理级状态，用于优化回退和避免无用的获取 */
	int			fetch_ct_2;		/* 最小（已执行的获取次数，2） */
	bool		eof_reached;	/* 如果最后的获取到达了 EOF 则为真 */

	/* 用于异步执行 */
	bool		async_capable;	/* 启用异步能力逻辑？ */

	/* 工作内存上下文 */
	MemoryContext batch_cxt;	/* 持有当前批次元组的上下文 */
	MemoryContext temp_cxt;		/* 每个元组临时数据的上下文 */

	int			fetch_size;		/* 每次获取的元组数量 */
} PgFdwScanState;

/*
 * 外部插入/更新/删除操作的执行状态。
 */
typedef struct PgFdwModifyState
{
	Relation	rel;			/* 外部表的 relcache 入口 */
	AttInMetadata *attinmeta;	/* 属性数据类型转换元数据 */

	/* 用于远程查询执行 */
	PGconn	   *conn;			/* 扫描的连接 */
	PgFdwConnState *conn_state; /* 每连接的额外状态 */
	char	   *p_name;			/* 已创建的准备语句名称 */

	/* 提取的 fdw_private 数据 */
	char	   *query;			/* INSERT/UPDATE/DELETE 命令的文本 */
	char	   *orig_query;		/* INSERT 命令的原始文本 */
	List	   *target_attrs;	/* 目标属性编号列表 */
	int			values_end;		/* 直到 VALUES 结束的长度 */
	int			batch_size;		/* FDW 选项 "batch_size" 的值 */
	bool		has_returning;	/* 是否有 RETURNING 子句？ */
	List	   *retrieved_attrs;	/* 通过 RETURNING 检索到的属性编号 */

	/* 准备语句参数的信息 */
	AttrNumber	ctidAttno;		/* 输入 resjunk ctid 列的 attnum */
	int			p_nums;			/* 要传输的参数数量 */
	FmgrInfo   *p_flinfo;		/* 对它们的输出转换函数 */

	/* 批处理操作相关内容 */
	int			num_slots;		/* 要插入的槽位数量 */

	/* 工作内存上下文 */
	MemoryContext temp_cxt;		/* 每个元组临时数据的上下文 */

	/* 如果子计划结果关系，更新行移动 */
	struct PgFdwModifyState *aux_fmstate;	/* 外部插入状态，如果已创建 */
} PgFdwModifyState;

/*
 * 修改外部表的外部扫描执行状态。
 */
typedef struct PgFdwDirectModifyState
{
	Relation	rel;			/* 外部表的 relcache 入口 */
	AttInMetadata *attinmeta;	/* 属性数据类型转换元数据 */

	/* 提取的 fdw_private 数据 */
	char	   *query;			/* UPDATE/DELETE 命令的文本 */
	bool		has_returning;	/* 是否有 RETURNING 子句？ */
	List	   *retrieved_attrs;	/* 通过 RETURNING 检索到的属性编号 */
	bool		set_processed;	/* 我们是否设置命令 es_processed？ */

	/* 用于远程查询执行 */
	PGconn	   *conn;			/* 更新的连接 */
	PgFdwConnState *conn_state; /* 每连接的额外状态 */
	int			numParams;		/* 传递给查询的参数数量 */
	FmgrInfo   *param_flinfo;	/* 对它们的输出转换函数 */
	List	   *param_exprs;	/* 参数值的可执行表达式 */
	const char **param_values;	/* 查询参数的文本值 */

	/* 用于存储结果元组 */
	PGresult   *result;			/* 查询的结果 */
	int			num_tuples;		/* 结果元组的数量 */
	int			next_tuple;		/* 下一个要返回的索引 */
	Relation	resultRel;		/* 目标关系的 relcache 入口 */
	AttrNumber *attnoMap;		/* 输入用户列的 attnums 数组 */
	AttrNumber	ctidAttno;		/* 输入 ctid 列的 attnum */
	AttrNumber	oidAttno;		/* 输入 oid 列的 attnum */
	bool		hasSystemCols;	/* resultRel 是否有系统列？ */

	/* 工作内存上下文 */
	MemoryContext temp_cxt;		/* 每个元组临时数据的上下文 */
} PgFdwDirectModifyState;

/*
 * 用于分析外部表的工作区。
 */
typedef struct PgFdwAnalyzeState
{
	Relation	rel;			/* 外部表的 relcache 入口 */
	AttInMetadata *attinmeta;	/* 属性数据类型转换元数据 */
	List	   *retrieved_attrs;	/* 查询中检索到的属性编号 */

	/* 收集的样本行 */
	HeapTuple  *rows;			/* 大小为 targrows 的数组 */
	int			targrows;		/* 样本行的目标数量 */
	int			numrows;		/* 收集的样本行数量 */

	/* 随机抽样用 */
	double		samplerows;		/* 获取的行数 */
	double		rowstoskip;		/* 在下一个样本之前要跳过的行数 */
	ReservoirStateData rstate;	/* 储水抽样的状态 */

	/* 工作内存上下文 */
	MemoryContext anl_cxt;		/* 每次分析生命期数据的上下文 */
	MemoryContext temp_cxt;		/* 每个元组临时数据的上下文 */
} PgFdwAnalyzeState;

/*
 * 此枚举描述了为 ForeignPath 保持的 fdw_private 列表的内容。
 * 我们存储：
 *
 * 1) 布尔标志，表示远程查询是否有最终排序
 * 2) 布尔标志，表示远程查询是否有 LIMIT 子句
 */
enum FdwPathPrivateIndex
{
	/* has-final-sort 标志（作为布尔节点） */
	FdwPathPrivateHasFinalSort,
	/* has-limit 标志（作为布尔节点） */
	FdwPathPrivateHasLimit
};

/* 传递给 estimate_path_cost_size() 的额外信息的结构 */
typedef struct
{
	PathTarget *target;
	bool		has_final_sort;
	bool		has_limit;
	double		limit_tuples;
	int64		count_est;
	int64		offset_est;
} PgFdwPathExtraData;

/*
 * 确定数据转换失败的属性。
 */
typedef struct ConversionLocation
{
	AttrNumber	cur_attno;		/* 正在处理的属性编号，或 0 */
	Relation	rel;			/* 正在处理的外部表，或 NULL */
	ForeignScanState *fsstate;	/* 正在处理的计划节点，或 NULL */
} ConversionLocation;

/* ec_member_matches_foreign 的回调参数 */
typedef struct
{
	Expr	   *current;		/* 当前表达式，如果尚未找到则为 NULL */
	List	   *already_used;	/* 已处理的表达式 */
} ec_member_foreign_arg;

/*
 * SQL 函数
 */
PG_FUNCTION_INFO_V1(postgres_fdw_handler);

/*
 * FDW 回调例程
 */
static void fc_postgresGetForeignRelSize(PlannerInfo *fc_root,
									  RelOptInfo *fc_baserel,
									  Oid fc_foreigntableid);
static void fc_postgresGetForeignPaths(PlannerInfo *fc_root,
									RelOptInfo *fc_baserel,
									Oid fc_foreigntableid);
static ForeignScan *fc_postgresGetForeignPlan(PlannerInfo *fc_root,
										   RelOptInfo *fc_foreignrel,
										   Oid fc_foreigntableid,
										   ForeignPath *fc_best_path,
										   List *fc_tlist,
										   List *fc_scan_clauses,
										   Plan *fc_outer_plan);
static void fc_postgresBeginForeignScan(ForeignScanState *fc_node, int fc_eflags);
static TupleTableSlot *fc_postgresIterateForeignScan(ForeignScanState *fc_node);
static void fc_postgresReScanForeignScan(ForeignScanState *fc_node);
static void fc_postgresEndForeignScan(ForeignScanState *fc_node);
static void fc_postgresAddForeignUpdateTargets(PlannerInfo *fc_root,
											Index fc_rtindex,
											RangeTblEntry *fc_target_rte,
											Relation fc_target_relation);
static List *fc_postgresPlanForeignModify(PlannerInfo *fc_root,
									   ModifyTable *fc_plan,
									   Index fc_resultRelation,
									   int fc_subplan_index);
static void fc_postgresBeginForeignModify(ModifyTableState *fc_mtstate,
									   ResultRelInfo *fc_resultRelInfo,
									   List *fc_fdw_private,
									   int fc_subplan_index,
									   int fc_eflags);
static TupleTableSlot *fc_postgresExecForeignInsert(EState *fc_estate,
												 ResultRelInfo *fc_resultRelInfo,
												 TupleTableSlot *fc_slot,
												 TupleTableSlot *fc_planSlot);
static TupleTableSlot **fc_postgresExecForeignBatchInsert(EState *fc_estate,
													   ResultRelInfo *fc_resultRelInfo,
													   TupleTableSlot **fc_slots,
													   TupleTableSlot **fc_planSlots,
													   int *fc_numSlots);
static int	fc_postgresGetForeignModifyBatchSize(ResultRelInfo *fc_resultRelInfo);
static TupleTableSlot *fc_postgresExecForeignUpdate(EState *fc_estate,
												 ResultRelInfo *fc_resultRelInfo,
												 TupleTableSlot *fc_slot,
												 TupleTableSlot *fc_planSlot);
static TupleTableSlot *fc_postgresExecForeignDelete(EState *fc_estate,
												 ResultRelInfo *fc_resultRelInfo,
												 TupleTableSlot *fc_slot,
												 TupleTableSlot *fc_planSlot);
static void fc_postgresEndForeignModify(EState *fc_estate,
									 ResultRelInfo *fc_resultRelInfo);
static void fc_postgresBeginForeignInsert(ModifyTableState *fc_mtstate,
									   ResultRelInfo *fc_resultRelInfo);
static void fc_postgresEndForeignInsert(EState *fc_estate,
									 ResultRelInfo *fc_resultRelInfo);
static int	fc_postgresIsForeignRelUpdatable(Relation fc_rel);
static bool fc_postgresPlanDirectModify(PlannerInfo *fc_root,
									 ModifyTable *fc_plan,
									 Index fc_resultRelation,
									 int fc_subplan_index);
static void fc_postgresBeginDirectModify(ForeignScanState *fc_node, int fc_eflags);
static TupleTableSlot *fc_postgresIterateDirectModify(ForeignScanState *fc_node);
static void fc_postgresEndDirectModify(ForeignScanState *fc_node);
static void fc_postgresExplainForeignScan(ForeignScanState *fc_node,
									   ExplainState *fc_es);
static void fc_postgresExplainForeignModify(ModifyTableState *fc_mtstate,
										 ResultRelInfo *fc_rinfo,
										 List *fc_fdw_private,
										 int fc_subplan_index,
										 ExplainState *fc_es);
static void fc_postgresExplainDirectModify(ForeignScanState *fc_node,
										ExplainState *fc_es);
static void fc_postgresExecForeignTruncate(List *fc_rels,
										DropBehavior fc_behavior,
										bool fc_restart_seqs);
static bool fc_postgresAnalyzeForeignTable(Relation fc_relation,
										AcquireSampleRowsFunc *fc_func,
										BlockNumber *fc_totalpages);
static List *fc_postgresImportForeignSchema(ImportForeignSchemaStmt *fc_stmt,
										 Oid fc_serverOid);
static void fc_postgresGetForeignJoinPaths(PlannerInfo *fc_root,
										RelOptInfo *fc_joinrel,
										RelOptInfo *fc_outerrel,
										RelOptInfo *fc_innerrel,
										JoinType fc_jointype,
										JoinPathExtraData *fc_extra);
static bool fc_postgresRecheckForeignScan(ForeignScanState *fc_node,
									   TupleTableSlot *fc_slot);
static void fc_postgresGetForeignUpperPaths(PlannerInfo *fc_root,
										 UpperRelationKind fc_stage,
										 RelOptInfo *fc_input_rel,
										 RelOptInfo *fc_output_rel,
										 void *fc_extra);
static bool fc_postgresIsForeignPathAsyncCapable(ForeignPath *fc_path);
static void fc_postgresForeignAsyncRequest(AsyncRequest *fc_areq);
static void fc_postgresForeignAsyncConfigureWait(AsyncRequest *fc_areq);
static void fc_postgresForeignAsyncNotify(AsyncRequest *fc_areq);

/*
 * 辅助函数
 */
static void fc_estimate_path_cost_size(PlannerInfo *fc_root,
									RelOptInfo *fc_foreignrel,
									List *fc_param_join_conds,
									List *fc_pathkeys,
									PgFdwPathExtraData *fc_fpextra,
									double *fc_p_rows, int *fc_p_width,
									Cost *fc_p_startup_cost, Cost *fc_p_total_cost);
static void fc_get_remote_estimate(const char *fc_sql,
								PGconn *fc_conn,
								double *fc_rows,
								int *fc_width,
								Cost *fc_startup_cost,
								Cost *fc_total_cost);
static void fc_adjust_foreign_grouping_path_cost(PlannerInfo *fc_root,
											  List *fc_pathkeys,
											  double fc_retrieved_rows,
											  double fc_width,
											  double fc_limit_tuples,
											  Cost *fc_p_startup_cost,
											  Cost *fc_p_run_cost);
static bool fc_ec_member_matches_foreign(PlannerInfo *fc_root, RelOptInfo *fc_rel,
									  EquivalenceClass *fc_ec, EquivalenceMember *fc_em,
									  void *fc_arg);
static void fc_create_cursor(ForeignScanState *fc_node);
static void fc_fetch_more_data(ForeignScanState *fc_node);
static void fc_close_cursor(PGconn *fc_conn, unsigned int fc_cursor_number,
						 PgFdwConnState *fc_conn_state);
static PgFdwModifyState *fc_create_foreign_modify(EState *fc_estate,
											   RangeTblEntry *fc_rte,
											   ResultRelInfo *fc_resultRelInfo,
											   CmdType fc_operation,
											   Plan *fc_subplan,
											   char *fc_query,
											   List *fc_target_attrs,
											   int fc_len,
											   bool fc_has_returning,
											   List *fc_retrieved_attrs);
static TupleTableSlot **fc_execute_foreign_modify(EState *fc_estate,
											   ResultRelInfo *fc_resultRelInfo,
											   CmdType fc_operation,
											   TupleTableSlot **fc_slots,
											   TupleTableSlot **fc_planSlots,
											   int *fc_numSlots);
static void fc_prepare_foreign_modify(PgFdwModifyState *fc_fmstate);
static const char **fc_convert_prep_stmt_params(PgFdwModifyState *fc_fmstate,
											 ItemPointer fc_tupleid,
											 TupleTableSlot **fc_slots,
											 int fc_numSlots);
static void fc_store_returning_result(PgFdwModifyState *fc_fmstate,
								   TupleTableSlot *fc_slot, PGresult *fc_res);
static void fc_finish_foreign_modify(PgFdwModifyState *fc_fmstate);
static void fc_deallocate_query(PgFdwModifyState *fc_fmstate);
static List *fc_build_remote_returning(Index fc_rtindex, Relation fc_rel,
									List *fc_returningList);
static void fc_rebuild_fdw_scan_tlist(ForeignScan *fc_fscan, List *fc_tlist);
static void fc_execute_dml_stmt(ForeignScanState *fc_node);
static TupleTableSlot *fc_get_returning_data(ForeignScanState *fc_node);
static void fc_init_returning_filter(PgFdwDirectModifyState *fc_dmstate,
								  List *fc_fdw_scan_tlist,
								  Index fc_rtindex);
static TupleTableSlot *fc_apply_returning_filter(PgFdwDirectModifyState *fc_dmstate,
											  ResultRelInfo *fc_resultRelInfo,
											  TupleTableSlot *fc_slot,
											  EState *fc_estate);
static void fc_prepare_query_params(PlanState *fc_node,
								 List *fc_fdw_exprs,
								 int fc_numParams,
								 FmgrInfo **fc_param_flinfo,
								 List **fc_param_exprs,
								 const char ***fc_param_values);
static void fc_process_query_params(ExprContext *fc_econtext,
								 FmgrInfo *fc_param_flinfo,
								 List *fc_param_exprs,
								 const char **fc_param_values);
static int	fc_postgresAcquireSampleRowsFunc(Relation fc_relation, int fc_elevel,
										  HeapTuple *fc_rows, int fc_targrows,
										  double *fc_totalrows,
										  double *fc_totaldeadrows);
static void fc_analyze_row_processor(PGresult *fc_res, int fc_row,
								  PgFdwAnalyzeState *fc_astate);
static void fc_produce_tuple_asynchronously(AsyncRequest *fc_areq, bool fc_fetch);
static void fc_fetch_more_data_begin(AsyncRequest *fc_areq);
static void fc_complete_pending_request(AsyncRequest *fc_areq);
static HeapTuple fc_make_tuple_from_result_row(PGresult *fc_res,
											int fc_row,
											Relation fc_rel,
											AttInMetadata *fc_attinmeta,
											List *fc_retrieved_attrs,
											ForeignScanState *fc_fsstate,
											MemoryContext fc_temp_context);
static void fc_conversion_error_callback(void *fc_arg);
static bool fc_foreign_join_ok(PlannerInfo *fc_root, RelOptInfo *fc_joinrel,
							JoinType fc_jointype, RelOptInfo *fc_outerrel, RelOptInfo *fc_innerrel,
							JoinPathExtraData *fc_extra);
static bool fc_foreign_grouping_ok(PlannerInfo *fc_root, RelOptInfo *fc_grouped_rel,
								Node *fc_havingQual);
static List *fc_get_useful_pathkeys_for_relation(PlannerInfo *fc_root,
											  RelOptInfo *fc_rel);
static List *fc_get_useful_ecs_for_relation(PlannerInfo *fc_root, RelOptInfo *fc_rel);
static void fc_add_paths_with_pathkeys_for_rel(PlannerInfo *fc_root, RelOptInfo *fc_rel,
											Path *fc_epq_path);
static void fc_add_foreign_grouping_paths(PlannerInfo *fc_root,
									   RelOptInfo *fc_input_rel,
									   RelOptInfo *fc_grouped_rel,
									   GroupPathExtraData *fc_extra);
static void fc_add_foreign_ordered_paths(PlannerInfo *fc_root,
									  RelOptInfo *fc_input_rel,
									  RelOptInfo *fc_ordered_rel);
static void fc_add_foreign_final_paths(PlannerInfo *fc_root,
									RelOptInfo *fc_input_rel,
									RelOptInfo *fc_final_rel,
									FinalPathExtraData *fc_extra);
static void fc_apply_server_options(PgFdwRelationInfo *fc_fpinfo);
static void fc_apply_table_options(PgFdwRelationInfo *fc_fpinfo);
static void fc_merge_fdw_options(PgFdwRelationInfo *fc_fpinfo,
							  const PgFdwRelationInfo *fc_fpinfo_o,
							  const PgFdwRelationInfo *fc_fpinfo_i);
static int	fc_get_batch_size_option(Relation fc_rel);


/*
 * 外部数据包装器处理函数：返回一个结构，包含指向
 * 我的回调例程的指针。
 */
Datum postgres_fdw_handler(PG_FUNCTION_ARGS)
{
	FdwRoutine *fc_routine = makeNode(FdwRoutine);

	/* 扫描外部表的函数 */
	fc_routine->GetForeignRelSize = fc_postgresGetForeignRelSize;
	fc_routine->GetForeignPaths = fc_postgresGetForeignPaths;
	fc_routine->GetForeignPlan = fc_postgresGetForeignPlan;
	fc_routine->BeginForeignScan = fc_postgresBeginForeignScan;
	fc_routine->IterateForeignScan = fc_postgresIterateForeignScan;
	fc_routine->ReScanForeignScan = fc_postgresReScanForeignScan;
	fc_routine->EndForeignScan = fc_postgresEndForeignScan;

	/* 更新外部表的函数 */
	fc_routine->AddForeignUpdateTargets = fc_postgresAddForeignUpdateTargets;
	fc_routine->PlanForeignModify = fc_postgresPlanForeignModify;
	fc_routine->BeginForeignModify = fc_postgresBeginForeignModify;
	fc_routine->ExecForeignInsert = fc_postgresExecForeignInsert;
	fc_routine->ExecForeignBatchInsert = fc_postgresExecForeignBatchInsert;
	fc_routine->GetForeignModifyBatchSize = fc_postgresGetForeignModifyBatchSize;
	fc_routine->ExecForeignUpdate = fc_postgresExecForeignUpdate;
	fc_routine->ExecForeignDelete = fc_postgresExecForeignDelete;
	fc_routine->EndForeignModify = fc_postgresEndForeignModify;
	fc_routine->BeginForeignInsert = fc_postgresBeginForeignInsert;
	fc_routine->EndForeignInsert = fc_postgresEndForeignInsert;
	fc_routine->IsForeignRelUpdatable = fc_postgresIsForeignRelUpdatable;
	fc_routine->PlanDirectModify = fc_postgresPlanDirectModify;
	fc_routine->BeginDirectModify = fc_postgresBeginDirectModify;
	fc_routine->IterateDirectModify = fc_postgresIterateDirectModify;
	fc_routine->EndDirectModify = fc_postgresEndDirectModify;

	/* EvalPlanQual 重新检查的函数 */
	fc_routine->RecheckForeignScan = fc_postgresRecheckForeignScan;
	/* 支持函数用于 EXPLAIN */
	fc_routine->ExplainForeignScan = fc_postgresExplainForeignScan;
	fc_routine->ExplainForeignModify = fc_postgresExplainForeignModify;
	fc_routine->ExplainDirectModify = fc_postgresExplainDirectModify;

	/* 支持函数用于 TRUNCATE */
	fc_routine->ExecForeignTruncate = fc_postgresExecForeignTruncate;

	/* 支持函数用于 ANALYZE */
	fc_routine->AnalyzeForeignTable = fc_postgresAnalyzeForeignTable;

	/* 支持函数用于 IMPORT FOREIGN SCHEMA */
	fc_routine->ImportForeignSchema = fc_postgresImportForeignSchema;

	/* 支持函数用于连接下推 */
	fc_routine->GetForeignJoinPaths = fc_postgresGetForeignJoinPaths;

	/* 支持函数用于上层关系下推 */
	fc_routine->GetForeignUpperPaths = fc_postgresGetForeignUpperPaths;

	/* 支持函数用于异步执行 */
	fc_routine->IsForeignPathAsyncCapable = fc_postgresIsForeignPathAsyncCapable;
	fc_routine->ForeignAsyncRequest = fc_postgresForeignAsyncRequest;
	fc_routine->ForeignAsyncConfigureWait = fc_postgresForeignAsyncConfigureWait;
	fc_routine->ForeignAsyncNotify = fc_postgresForeignAsyncNotify;

	PG_RETURN_POINTER(fc_routine);
}

/*
 * postgresGetForeignRelSize
 *		估计扫描结果的行数和宽度
 *
 * 我们应该在这里考虑所有 baserestrictinfo 子句的影响，但
 * 不考虑任何连接子句。
 */
static void fc_postgresGetForeignRelSize(PlannerInfo *fc_root,
						  RelOptInfo *fc_baserel,
						  Oid fc_foreigntableid)
{
	PgFdwRelationInfo *fc_fpinfo;
	ListCell   *fc_lc;
	RangeTblEntry *fc_rte = planner_rt_fetch(fc_baserel->relid, fc_root);

	/*
	 * 我们使用 PgFdwRelationInfo 将各种信息传递给后续
	 * 函数。
	 */
	fc_fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
	fc_baserel->fdw_private = (void *) fc_fpinfo;

	/* 基础外部表需始终下推。 */
	fc_fpinfo->pushdown_safe = true;

	/* 查找外部表目录信息。 */
	fc_fpinfo->table = GetForeignTable(fc_foreigntableid);
	fc_fpinfo->server = GetForeignServer(fc_fpinfo->table->serverid);

	/*
	 * 提取用户可设置的选项值。注意，per-table 设置的
	 * use_remote_estimate、fetch_size 和 async_capable 分别
	 * 会覆盖 per-server 配置。
	 */
	fc_fpinfo->use_remote_estimate = false;
	fc_fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
	fc_fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
	fc_fpinfo->shippable_extensions = NIL;
	fc_fpinfo->fetch_size = 100;
	fc_fpinfo->async_capable = false;

	fc_apply_server_options(fc_fpinfo);
	fc_apply_table_options(fc_fpinfo);

	/*
	 * 如果表或服务器配置为使用远程估计，
	 * 确定在规划期间使用哪个用户进行远程访问。此
	 * 应与 ExecCheckRTEPerms() 的操作相匹配。如果由于权限不足而失败，
	 * 查询在运行时也会失败。
	 */
	if (fc_fpinfo->use_remote_estimate)
	{
		Oid			fc_userid = fc_rte->checkAsUser ? fc_rte->checkAsUser : GetUserId();

		fc_fpinfo->user = GetUserMapping(fc_userid, fc_fpinfo->server->serverid);
	}
	else
		fc_fpinfo->user = NULL;

	/*
	 * 确定哪些 baserestrictinfo 子句可以发送到远程服务器，哪些不能。
	 */
	classifyConditions(fc_root, fc_baserel, fc_baserel->baserestrictinfo,
					   &fc_fpinfo->remote_conds, &fc_fpinfo->local_conds);

	/*
	 * 确定哪些属性需要从远程服务器检索。这些包括所有用于连接或最终输出的 attrs，以及在 local_conds 中使用的所有 attrs。（注意：如果我们最终使用参数化扫描，可能会将某些连接子句发送至远程，因此我们实际上不需要检索它们所使用的列。不过，这种情况似乎不值得检测。）
	 */
	fc_fpinfo->attrs_used = NULL;
	pull_varattnos((Node *) fc_baserel->reltarget->exprs, fc_baserel->relid,
				   &fc_fpinfo->attrs_used);
	foreach(fc_lc, fc_fpinfo->local_conds)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_lc);

		pull_varattnos((Node *) fc_rinfo->clause, fc_baserel->relid,
					   &fc_fpinfo->attrs_used);
	}

	/*
	 * 计算 local_conds 的选择性和成本，以便我们不必对每条路径重新计算。对于这些条件，我们能做的最好的是根据本地统计数据来估计选择性。
	 */
	fc_fpinfo->local_conds_sel = clauselist_selectivity(fc_root,
													 fc_fpinfo->local_conds,
													 fc_baserel->relid,
													 JOIN_INNER,
													 NULL);

	cost_qual_eval(&fc_fpinfo->local_conds_cost, fc_fpinfo->local_conds, fc_root);

	/*
	 * 将检索的行数和缓存的关系成本设置为某个负值，以便于在对 estimate_path_cost_size 的一次调用（通常是第一次）期间检测它们何时被设置为某些合理的值。
	 */
	fc_fpinfo->retrieved_rows = -1;
	fc_fpinfo->rel_startup_cost = -1;
	fc_fpinfo->rel_total_cost = -1;

	/*
	 * 如果表或服务器配置为使用远程估算，连接到外部服务器并执行 EXPLAIN 以估算由限制子句选择的行数，以及平均行宽。否则，使用我们本地的任何统计数据进行估算，方法类似于普通表。
	 */
	if (fc_fpinfo->use_remote_estimate)
	{
		/*
		 * 借助远程服务器获取成本/大小估算。将值保存在 fpinfo 中，以便生成基本的外部路径时无需重复执行。
		 */
		fc_estimate_path_cost_size(fc_root, fc_baserel, NIL, NIL, NULL,
								&fc_fpinfo->rows, &fc_fpinfo->width,
								&fc_fpinfo->startup_cost, &fc_fpinfo->total_cost);

		/* 将估算的 baserel 大小报告给规划器。 */
		fc_baserel->rows = fc_fpinfo->rows;
		fc_baserel->reltarget->width = fc_fpinfo->width;
	}
	else
	{
		/*
		 * 如果外部表从未进行过 ANALYZE，则其 reltuples < 0，表示“未知”。如果我们不被允许咨询远程服务器，我们的做法有限，但我们可以使用类似 plancat.c 对空关系的处理的黑客方法：使用最小大小估计为 10 页，并按基于列数据类型的宽度估计值进行划分，以获得相应的元组数。
		 */
		if (fc_baserel->tuples < 0)
		{
			fc_baserel->pages = 10;
			fc_baserel->tuples =
				(10 * BLCKSZ) / (fc_baserel->reltarget->width +
								 MAXALIGN(SizeofHeapTupleHeader));
		}

		/* 尽可能使用本地统计数据估算 baserel 大小。 */
		set_baserel_size_estimates(fc_root, fc_baserel);

		/* 填充基本上是虚假的成本估算以供后续使用。 */
		fc_estimate_path_cost_size(fc_root, fc_baserel, NIL, NIL, NULL,
								&fc_fpinfo->rows, &fc_fpinfo->width,
								&fc_fpinfo->startup_cost, &fc_fpinfo->total_cost);
	}

	/*
	 * fpinfo->relation_name 获取外部表 RTE 的数值 rangetable 索引。（如果该查询被 EXPLAIN，届时我们会将其转换为可读字符串。）
	 */
	fc_fpinfo->relation_name = psprintf("%u", fc_baserel->relid);

	/* 没有外部和内部关系。 */
	fc_fpinfo->make_outerrel_subquery = false;
	fc_fpinfo->make_innerrel_subquery = false;
	fc_fpinfo->lower_subquery_rels = NULL;
	/* 设置关系索引。 */
	fc_fpinfo->relation_index = fc_baserel->relid;
}

/*
 * get_useful_ecs_for_relation
 *		确定哪些等价类可能涉及此关系的有用排序。
 *
 * 在某些方面，这个函数是核心函数
 * pathkeys_useful_for_merging 的镜像：对于常规表，我们知道我们有
 * 什么索引并希望测试它们是否有用。对于外部表，我们不知道远程
 * 端存在什么索引，但希望推测如果存在，我们希望使用哪些索引。
 *
 * 该函数返回一个潜在有用的等价类列表，但不保证存在一个
 * 只包含给定关系中 Vars 的等价成员。例如，给定 ft1 JOIN t1 ON
 * ft1.x + t1.x = 0，该函数将表示包含 ft1.x + t1.x 的等价类是
 * 潜在有用的。假设 ft1 是远程的而 t1 是本地的（或在不同的服务器上），
 * 最终会发现无法生成有用的 ORDER BY 子句。我们在这里不需要搞清楚这一点；
 * 我们只关心识别相关的等价类。
 */
static List * fc_get_useful_ecs_for_relation(PlannerInfo *fc_root, RelOptInfo *fc_rel)
{
	List	   *fc_useful_eclass_list = NIL;
	ListCell   *fc_lc;
	Relids		fc_relids;

	/*
	 * 首先，考虑是否有任何活动的等价类可能对
	 * 该关系的合并连接有用。
	 */
	if (fc_rel->has_eclass_joins)
	{
		foreach(fc_lc, fc_root->eq_classes)
		{
			EquivalenceClass *fc_cur_ec = (EquivalenceClass *) lfirst(fc_lc);

			if (eclass_useful_for_merging(fc_root, fc_cur_ec, fc_rel))
				fc_useful_eclass_list = lappend(fc_useful_eclass_list, fc_cur_ec);
		}
	}

	/*
	 * 接下来，考虑是否有任何非等价类可导出的连接子句
	 * 可以合并连接。如果 joininfo 列表为空，我们可以快速退出。
	 */
	if (fc_rel->joininfo == NIL)
		return fc_useful_eclass_list;

	/* 如果这是一个子关系，我们必须使用最顶层的父关系进行搜索。 */
	if (IS_OTHER_REL(fc_rel))
	{
		Assert(!bms_is_empty(fc_rel->top_parent_relids));
		fc_relids = fc_rel->top_parent_relids;
	}
	else
		fc_relids = fc_rel->relids;

	/* 逐个检查每个连接子句。 */
	foreach(fc_lc, fc_rel->joininfo)
	{
		RestrictInfo *fc_restrictinfo = (RestrictInfo *) lfirst(fc_lc);

		/* 仅考虑可合并连接的子句 */
		if (fc_restrictinfo->mergeopfamilies == NIL)
			continue;

		/* 确保我们拥有规范的等价类。 */
		update_mergeclause_eclasses(fc_root, fc_restrictinfo);

		/*
		 * restrictinfo->mergeopfamilies != NIL 足以保证
		 * left_ec 和 right_ec 将被初始化，具体请参阅
		 * distribute_qual_to_rels 中的注释。
		 *
		 * 我们希望识别这个可合并连接子句的哪一侧
		 * 包含由这个 RelOptInfo 生成的关系的列。我们
		 * 测试的是重叠，而不是包含，因为两侧可能
		 * 存在额外的关系。例如，假设我们有类似于
		 * ((A JOIN B ON A.x = B.x) JOIN C ON A.y = C.y)
		 * LEFT JOIN D ON A.y = D.y 的内容。输入关系可能是
		 * A 和 B 之间的 joinrel，我们将考虑连接子句
		 * A.y = D.y。relids 包含一个未参与连接类的
		 * 关系（B），而该子句左侧的等价类包含一个未参与
		 * 输入关系的关系（C）。尽管我们只有重叠而不是
		 * 任何方向上的包含，A.y 作为一个排序列仍然是
		 * 潜在有用的。
		 *
		 * 请注意，relids 甚至可能与连接子句的任一侧
		 * 没有重叠。例如，考虑 A LEFT JOIN B ON A.x = B.x
		 * AND A.x = 1。子句 A.x = 1 将出现在 B 的
		 * joininfo 列表中，但与 B 的任一侧都没有重叠。
		 * 在这种情况下，我们只需跳过这个连接子句，
		 * 因为它并未对该关系建议有用的排序顺序。
		 */
		if (bms_overlap(fc_relids, fc_restrictinfo->right_ec->ec_relids))
			fc_useful_eclass_list = list_append_unique_ptr(fc_useful_eclass_list,
														fc_restrictinfo->right_ec);
		else if (bms_overlap(fc_relids, fc_restrictinfo->left_ec->ec_relids))
			fc_useful_eclass_list = list_append_unique_ptr(fc_useful_eclass_list,
														fc_restrictinfo->left_ec);
	}

	return fc_useful_eclass_list;
}

/*
 * 获取对关系有用的路径键
 *		确定关系的哪些排序可能是有用的。
 *
 * 获取已排序的数据可能是有用的，因为请求的
 * 排序匹配我们计划的整体查询的最终输出排序，或者因为它启用了高效的归并连接。在这里，我们尝试
 * 弄清楚要考虑哪些路径键。
 */
static List * fc_get_useful_pathkeys_for_relation(PlannerInfo *fc_root, RelOptInfo *fc_rel)
{
	List	   *fc_useful_pathkeys_list = NIL;
	List	   *fc_useful_eclass_list;
	PgFdwRelationInfo *fc_fpinfo = (PgFdwRelationInfo *) fc_rel->fdw_private;
	EquivalenceClass *fc_query_ec = NULL;
	ListCell   *fc_lc;

	/*
	 * 将查询路径键推送到远程服务器总是值得
	 * 考虑，因为这可以让我们避免本地排序。
	 */
	fc_fpinfo->qp_is_pushdown_safe = false;
	if (fc_root->query_pathkeys)
	{
		bool		fc_query_pathkeys_ok = true;

		foreach(fc_lc, fc_root->query_pathkeys)
		{
			PathKey    *fc_pathkey = (PathKey *) lfirst(fc_lc);

			/*
			 * 规划者和执行者没有任何聪明的策略来
			 * 处理按查询路径键的前缀排序的数据并
			 * 将其排序为所有这些路径键。我们只会
			 * 最终对整个数据集进行重新排序。因此，除非我们能推送
			 * 下所有查询路径键，否则就放弃吧。
			 */
			if (!is_foreign_pathkey(fc_root, fc_rel, fc_pathkey))
			{
				fc_query_pathkeys_ok = false;
				break;
			}
		}

		if (fc_query_pathkeys_ok)
		{
			fc_useful_pathkeys_list = list_make1(list_copy(fc_root->query_pathkeys));
			fc_fpinfo->qp_is_pushdown_safe = true;
		}
	}

	/*
	 * 即使我们不使用远程估算，让远程端进行
	 * 排序通常也不会比本地执行的效果差，如果
	 * 远程端能以正确的顺序生成数据而根本不需要
	 * 排序，效果可能会更好。然而，我们接下来要做的事情是
	 * 尝试生成看起来对可能的归并连接有前景的路径键，
	 * 这更具投机性。错误的选择可能会造成相当大的损害，
	 * 所以如果我们无法使用远程估算就退缩吧。
	 */
	if (!fc_fpinfo->use_remote_estimate)
		return fc_useful_pathkeys_list;

	/* 获取有趣的等价类列表。 */
	fc_useful_eclass_list = fc_get_useful_ecs_for_relation(fc_root, fc_rel);

	/* 提取查询的唯一等价类（如果有的话），以便我们不再考虑它。 */
	if (list_length(fc_root->query_pathkeys) == 1)
	{
		PathKey    *fc_query_pathkey = linitial(fc_root->query_pathkeys);

		fc_query_ec = fc_query_pathkey->pk_eclass;
	}

	/*
	 * 作为一种启发式方法，我们在这里考虑的唯一路径键是长度
	 * 为一的键。显然可以考虑更多的路径键，但由于我们选择
	 * 考虑的每一个都会导致往返远程端，所以我们
	 * 需要在这里稍微谨慎一些。能拥有有关远程索引定义的本地
	 * 缓存信息会非常好……
	 */
	foreach(fc_lc, fc_useful_eclass_list)
	{
		EquivalenceClass *fc_cur_ec = lfirst(fc_lc);
		PathKey    *fc_pathkey;

		/* 如果与我们之前做的冗余，则跳过。 */
		if (fc_cur_ec == fc_query_ec)
			continue;

		/* 如果等价类的操作族无法推送，则无法进行排序。 */
		if (!is_shippable(linitial_oid(fc_cur_ec->ec_opfamilies),
						  OperatorFamilyRelationId, fc_fpinfo))
			continue;

		/* 如果此关系没有可推送的表达式，则跳过。 */
		if (find_em_for_rel(fc_root, fc_cur_ec, fc_rel) == NULL)
			continue;

		/* 看起来我们可以生成一个路径键，那么就执行它。 */
		fc_pathkey = make_canonical_pathkey(fc_root, fc_cur_ec,
										 linitial_oid(fc_cur_ec->ec_opfamilies),
										 BTLessStrategyNumber,
										 false);
		fc_useful_pathkeys_list = lappend(fc_useful_pathkeys_list,
									   list_make1(fc_pathkey));
	}

	return fc_useful_pathkeys_list;
}

/*
 * postgresGetForeignPaths
 *		为外部表的扫描创建可能的扫描路径
 */
static void fc_postgresGetForeignPaths(PlannerInfo *fc_root,
						RelOptInfo *fc_baserel,
						Oid fc_foreigntableid)
{
	PgFdwRelationInfo *fc_fpinfo = (PgFdwRelationInfo *) fc_baserel->fdw_private;
	ForeignPath *fc_path;
	List	   *fc_ppi_list;
	ListCell   *fc_lc;

	/*
	 * 创建最简单的ForeignScan路径节点并将其添加到基关系。这条路径
	 * 对应于常规表的SeqScan路径（尽管根据我们能够发送给远程的
	 * 基限制条件，那里实际上可能发生索引扫描）。我们已经完成了
	 * 估算此路径的成本和大小的所有工作。
	 *
	 * 尽管此路径不使用连接子句，但由于
	 * 在其tlist中出现的LATERAL引用，可能仍然需要
	 * 参数化。
	 */
	fc_path = create_foreignscan_path(fc_root, fc_baserel,
								   NULL,	/* 默认的 pathtarget */
								   fc_fpinfo->rows,
								   fc_fpinfo->startup_cost,
								   fc_fpinfo->total_cost,
								   NIL, /* 无 pathkeys */
								   fc_baserel->lateral_relids,
								   NULL,	/* 无额外计划 */
								   NIL);	/* 无fdw_private列表 */
	add_path(fc_baserel, (Path *) fc_path);

	/* 添加带路径键的路径 */
	fc_add_paths_with_pathkeys_for_rel(fc_root, fc_baserel, NULL);

	/*
	 * 如果我们不使用远程估算，在这里停止。我们没有办法
	 * 估算任何连接子句是否值得发送，因此
	 * 不要费心构建参数化路径。
	 */
	if (!fc_fpinfo->use_remote_estimate)
		return;

	/*
	 * 浏览所有连接子句来识别哪些外部
	 * 关系可以提供一个或多个安全的远程发送连接子句。
	 * 我们将为每个这样的外部关系构建一个参数化路径。
	 *
	 * 通过用 ParamPathInfo 节点表示每个候选外部
	 * 关系来方便管理这一点。然后我们可以直接使用
	 * ParamPathInfo 节点中的 ppi_clauses 列表作为
	 * 该关系的有趣连接子句列表。这解决了
	 * 可能存在多个安全连接子句的情况，
	 * 并确保我们考虑到仍需在本地强制执行的不安全连接子句
	 * （因为参数化路径机制坚持我们处理所有可移动子句）。
	 */
	fc_ppi_list = NIL;
	foreach(fc_lc, fc_baserel->joininfo)
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc);
		Relids		fc_required_outer;
		ParamPathInfo *fc_param_info;

		/* 检查子句是否可以移动到该关系 */
		if (!join_clause_is_movable_to(fc_rinfo, fc_baserel))
			continue;

		/* 查看是否安全发送到远程 */
		if (!is_foreign_expr(fc_root, fc_baserel, fc_rinfo->clause))
			continue;

		/* 计算结果路径所需的外部关系 */
		fc_required_outer = bms_union(fc_rinfo->clause_relids,
								   fc_baserel->lateral_relids);
		/* 我们不希望外部关系本身列在 required_outer 中 */
		fc_required_outer = bms_del_member(fc_required_outer, fc_baserel->relid);

		/*
		 * 此处可能无法为空，但如果为空，我们
		 * 将无法生成参数化路径。
		 */
		if (bms_is_empty(fc_required_outer))
			continue;

		/* 获取 ParamPathInfo */
		fc_param_info = get_baserel_parampathinfo(fc_root, fc_baserel,
											   fc_required_outer);
		Assert(fc_param_info != NULL);

		/*
		 * 除非我们已经拥有它，否则将其添加到列表中。测试指针相等性
		 * 是可以的，因为 get_baserel_parampathinfo 不会生成重复项。
		 */
		fc_ppi_list = list_append_unique_ptr(fc_ppi_list, fc_param_info);
	}

	/*
	 * 以上扫描仅检查了“通用”连接子句，而不是那些
	 * 被吸收到等价子句中的子句。看看我们是否能从
	 * 等价子句中得出任何结果。
	 */
	if (fc_baserel->has_eclass_joins)
	{
		/*
		 * 我们反复扫描 eclass 列表，寻找所属
		 * 外部关系的列引用（或表达式）。每次找到
		 * 一个时，我们生成一个对应的等价连接子句列表，
		 * 然后查看是否可以安全地发送到远程。重复直到没有
		 * 更多候选 EC 成员。
		 */
		ec_member_foreign_arg fc_arg;

		fc_arg.already_used = NIL;
		for (;;)
		{
			List	   *fc_clauses;

			/* 生成子句，跳过任何连接到 lateral_referencers 的子句 */
			fc_arg.current = NULL;
			fc_clauses = generate_implied_equalities_for_column(fc_root,
															 fc_baserel,
															 fc_ec_member_matches_foreign,
															 (void *) &fc_arg,
															 fc_baserel->lateral_referencers);

			/* 如果外部关系中没有更多表达式，则完成 */
			if (fc_arg.current == NULL)
			{
				Assert(fc_clauses == NIL);
				break;
			}

			/* 扫描提取的连接子句 */
			foreach(fc_lc, fc_clauses)
			{
				RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc);
				Relids		fc_required_outer;
				ParamPathInfo *fc_param_info;

				/* 检查子句是否可以移动到该关系 */
				if (!join_clause_is_movable_to(fc_rinfo, fc_baserel))
					continue;

				/* 查看是否安全发送到远程 */
				if (!is_foreign_expr(fc_root, fc_baserel, fc_rinfo->clause))
					continue;

				/* 计算结果路径所需的外部关系 */
				fc_required_outer = bms_union(fc_rinfo->clause_relids,
										   fc_baserel->lateral_relids);
				fc_required_outer = bms_del_member(fc_required_outer, fc_baserel->relid);
				if (bms_is_empty(fc_required_outer))
					continue;

				/* 获取 ParamPathInfo */
				fc_param_info = get_baserel_parampathinfo(fc_root, fc_baserel,
													   fc_required_outer);
				Assert(fc_param_info != NULL);

				/* 除非我们已经拥有它，否则将其添加到列表中 */
				fc_ppi_list = list_append_unique_ptr(fc_ppi_list, fc_param_info);
			}

			/* 再试一次，现在忽略这次找到的表达式 */
			fc_arg.already_used = lappend(fc_arg.already_used, fc_arg.current);
		}
	}

	/*
	 * 现在为每个有用的外部关系构建路径。
	 */
	foreach(fc_lc, fc_ppi_list)
	{
		ParamPathInfo *fc_param_info = (ParamPathInfo *) lfirst(fc_lc);
		double		fc_rows;
		int			fc_width;
		Cost		fc_startup_cost;
		Cost		fc_total_cost;

		/* 从远程获取成本估算 */
		fc_estimate_path_cost_size(fc_root, fc_baserel,
								fc_param_info->ppi_clauses, NIL, NULL,
								&fc_rows, &fc_width,
								&fc_startup_cost, &fc_total_cost);

		/*
		 * 目前 ppi_rows 不会被任何东西查看，但我们
		 * 仍然可以确保它符合我们对行计数的想法。
		 */
		fc_param_info->ppi_rows = fc_rows;

		/* 创建路径 */
		fc_path = create_foreignscan_path(fc_root, fc_baserel,
									   NULL,	/* 默认的 pathtarget */
									   fc_rows,
									   fc_startup_cost,
									   fc_total_cost,
									   NIL, /* 无 pathkeys */
									   fc_param_info->ppi_req_outer,
									   NULL,
									   NIL);	/* 无fdw_private列表 */
		add_path(fc_baserel, (Path *) fc_path);
	}
}

/*
 * postgresGetForeignPlan
 *		创建 ForeignScan 计划节点以实现选择的最佳路径
 */
static ForeignScan * fc_postgresGetForeignPlan(PlannerInfo *fc_root,
					   RelOptInfo *fc_foreignrel,
					   Oid fc_foreigntableid,
					   ForeignPath *fc_best_path,
					   List *fc_tlist,
					   List *fc_scan_clauses,
					   Plan *fc_outer_plan)
{
	PgFdwRelationInfo *fc_fpinfo = (PgFdwRelationInfo *) fc_foreignrel->fdw_private;
	Index		fc_scan_relid;
	List	   *fc_fdw_private;
	List	   *fc_remote_exprs = NIL;
	List	   *fc_local_exprs = NIL;
	List	   *fc_params_list = NIL;
	List	   *fc_fdw_scan_tlist = NIL;
	List	   *fc_fdw_recheck_quals = NIL;
	List	   *fc_retrieved_attrs;
	StringInfoData fc_sql;
	bool		fc_has_final_sort = false;
	bool		fc_has_limit = false;
	ListCell   *fc_lc;

	/*
	 * 获取由 postgresGetForeignUpperPaths() 创建的 FDW 私有数据（如果有的话）。
	 */
	if (fc_best_path->fdw_private)
	{
		fc_has_final_sort = boolVal(list_nth(fc_best_path->fdw_private,
										  FdwPathPrivateHasFinalSort));
		fc_has_limit = boolVal(list_nth(fc_best_path->fdw_private,
									 FdwPathPrivateHasLimit));
	}

	if (IS_SIMPLE_REL(fc_foreignrel))
	{
		/*
		 * 对于基本关系，将 scan_relid 设置为关系的 relid。
		 */
		fc_scan_relid = fc_foreignrel->relid;

		/*
		 * 在基本关系扫描中，我们必须应用给定的 scan_clauses。
		 *
		 * 将 scan_clauses 分为可以远程执行的和不能远程执行的。
		 * 之前由 classifyConditions 确定为安全或不安全的 baserestrictinfo 子句
		 * 在 fpinfo->remote_conds 和 fpinfo->local_conds 中找到。scan_clauses 列表中的其他内容
		 * 将是连接子句，我们必须检查其远程安全性。
		 *
		 * 注意：我们在这里看到的连接子句应该与之前由 postgresGetForeignPaths 查看的一模一样。
		 * 可能值得将当时进行的分类工作继续传递，而不是在这里重复它。
		 *
		 * 此代码必须与 "extract_actual_clauses(scan_clauses, false)"
		 * 匹配，除了有关远程与本地执行的附加决策。
		 */
		foreach(fc_lc, fc_scan_clauses)
		{
			RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_lc);

			/* 忽略任何伪常量，它们在其他地方处理 */
			if (fc_rinfo->pseudoconstant)
				continue;

			if (list_member_ptr(fc_fpinfo->remote_conds, fc_rinfo))
				fc_remote_exprs = lappend(fc_remote_exprs, fc_rinfo->clause);
			else if (list_member_ptr(fc_fpinfo->local_conds, fc_rinfo))
				fc_local_exprs = lappend(fc_local_exprs, fc_rinfo->clause);
			else if (is_foreign_expr(fc_root, fc_foreignrel, fc_rinfo->clause))
				fc_remote_exprs = lappend(fc_remote_exprs, fc_rinfo->clause);
			else
				fc_local_exprs = lappend(fc_local_exprs, fc_rinfo->clause);
		}

		/*
		 * 对于基本关系扫描，我们必须支持 EPQ 复查，这
		 * 应该重新检查所有远程资格。
		 */
		fc_fdw_recheck_quals = fc_remote_exprs;
	}
	else
	{
		/*
		 * 连接关系或上层关系 - 将 scan_relid 设置为 0。
		 */
		fc_scan_relid = 0;

		/*
		 * 对于连接关系，baserestrictinfo 为 NIL，我们现在不考虑
		 * 参数化，因此连接关系或上层关系都不应该有 scan_clauses。
		 */
		Assert(!fc_scan_clauses);

		/*
		 * 相反，我们从 fdw_private 结构中获取要应用的条件。
		 */
		fc_remote_exprs = extract_actual_clauses(fc_fpinfo->remote_conds, false);
		fc_local_exprs = extract_actual_clauses(fc_fpinfo->local_conds, false);

		/*
		 * 在这种情况下，我们将 fdw_recheck_quals 留空，因为我们从不需要
		 * 应用 EPQ 复查子句。对于连接关系，EPQ 复查在其他地方处理——
		 * 请参见 postgresGetForeignJoinPaths()。如果我们正在计划一个上层关系（即远程分组或聚合），
		 * 那么就没有 EPQ 可做，因为不允许使用 SELECT FOR UPDATE，实际上我们
		 * 不能将远程子句放入 fdw_recheck_quals，因为未经聚合的 Vars 不会在本地可用。
		 */

		/* 构建从外部服务器获取的列列表。 */
		fc_fdw_scan_tlist = build_tlist_to_deparse(fc_foreignrel);

		/*
		 * 确保外部计划生成一个与我们的扫描元组槽匹配的元组描述符。
		 * 此外，从外部计划的资格中移除本地条件，以免它们被
		 * 评估两次，一次由本地计划，一次由扫描。
		 */
		if (fc_outer_plan)
		{
			ListCell   *fc_lc;

			/*
			 * 现在，我们只考虑超出连接的分组和聚合。
			 * 涉及聚合或分组的查询不需要 EPQ 机制，因此不应在这里有外部计划。
			 */
			Assert(!IS_UPPER_REL(fc_foreignrel));

			/*
			 * 首先，如果可能的话，更新计划的资格列表。在某些情况下，
			 * 资格可能在最顶部计划级别以下强制执行，在这种情况下我们将无法移除它们；
			 * 这并不值得我们作更艰苦的努力。
			 */
			foreach(fc_lc, fc_local_exprs)
			{
				Node	   *fc_qual = lfirst(fc_lc);

				fc_outer_plan->qual = list_delete(fc_outer_plan->qual, fc_qual);

				/*
				 * 对于内连接，外部扫描计划的局部条件
				 * 也可以是连接条件的一部分。（它们也可能在合并条件或哈希条件中，
				 * 但我们不能触及那些，否则会破坏计划。）
				 */
				if (IsA(fc_outer_plan, NestLoop) ||
					IsA(fc_outer_plan, MergeJoin) ||
					IsA(fc_outer_plan, HashJoin))
				{
					Join	   *fc_join_plan = (Join *) fc_outer_plan;

					if (fc_join_plan->jointype == JOIN_INNER)
						fc_join_plan->joinqual = list_delete(fc_join_plan->joinqual,
														  fc_qual);
				}
			}

			/*
			 * 现在修复子计划的目标列表——这可能会导致在计划树顶部插入
			 * 一个结果节点。
			 */
			fc_outer_plan = change_plan_targetlist(fc_outer_plan, fc_fdw_scan_tlist,
												fc_best_path->path.parallel_safe);
		}
	}

	/*
	 * 构建要发送执行的查询字符串，并识别
	 * 作为参数发送的表达式。
	 */
	initStringInfo(&fc_sql);
	deparseSelectStmtForRel(&fc_sql, fc_root, fc_foreignrel, fc_fdw_scan_tlist,
							fc_remote_exprs, fc_best_path->path.pathkeys,
							fc_has_final_sort, fc_has_limit, false,
							&fc_retrieved_attrs, &fc_params_list);

	/* 记住 remote_exprs 以便可能被 postgresPlanDirectModify 使用 */
	fc_fpinfo->final_remote_exprs = fc_remote_exprs;

	/*
	 * 构建将可用于执行器的 fdw_private 列表。
	 * 列表中的项必须与枚举 FdwScanPrivateIndex 中的顺序匹配。
	 */
	fc_fdw_private = list_make3(makeString(fc_sql.data),
							 fc_retrieved_attrs,
							 makeInteger(fc_fpinfo->fetch_size));
	if (IS_JOIN_REL(fc_foreignrel) || IS_UPPER_REL(fc_foreignrel))
		fc_fdw_private = lappend(fc_fdw_private,
							  makeString(fc_fpinfo->relation_name));

	/*
	 * 为给定关系创建外部扫描节点。
	 *
	 * 请注意，远程参数表达式存储在完成的计划节点的 fdw_exprs
	 * 字段中；我们无法将它们保留在私有状态中，
	 * 因为那样它们就不再受后续计划处理的约束。
	 */
	return make_foreignscan(fc_tlist,
							fc_local_exprs,
							fc_scan_relid,
							fc_params_list,
							fc_fdw_private,
							fc_fdw_scan_tlist,
							fc_fdw_recheck_quals,
							fc_outer_plan);
}

/*
 * 为外部连接处理的扫描元组构造一个元组描述符。
 */
static TupleDesc fc_get_tupdesc_for_join_scan_tuples(ForeignScanState *fc_node)
{
	ForeignScan *fc_fsplan = (ForeignScan *) fc_node->ss.ps.plan;
	EState	   *fc_estate = fc_node->ss.ps.state;
	TupleDesc	fc_tupdesc;

	/*
	 * 核心代码已经根据 fsplan->fdw_scan_tlist 设置了扫描元组槽，
	 * 这个槽的 tupdesc 大多足够好，
	 * 但有一种情况是这样。 如果我们有任何整个行的行标识符变量，
	 * 它们的 vartype 可能是 RECORD，我们需要将其替换为
	 * 关联表的实际复合类型。这样确保了
	 * 当我们从远程服务器读取这些 ROW() 表达式值时，
	 * 我们可以将其转换为本地服务器知道的复合类型。
	 */
	fc_tupdesc = CreateTupleDescCopy(fc_node->ss.ss_ScanTupleSlot->tts_tupleDescriptor);
	for (int fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);
		Var		   *fc_var;
		RangeTblEntry *fc_rte;
		Oid			fc_reltype;

		/* 如果它不是通用 RECORD 属性，则无事可做 */
		if (fc_att->atttypid != RECORDOID || fc_att->atttypmod >= 0)
			continue;

		/*
		 * 如果我们无法识别引用的表，则不执行任何操作。 这可能会
		 * 在后面导致失败，但也许我们可以混过去。
		 */
		fc_var = (Var *) list_nth_node(TargetEntry, fc_fsplan->fdw_scan_tlist,
									fc_i)->expr;
		if (!IsA(fc_var, Var) || fc_var->varattno != 0)
			continue;
		fc_rte = list_nth(fc_estate->es_range_table, fc_var->varno - 1);
		if (fc_rte->rtekind != RTE_RELATION)
			continue;
		fc_reltype = get_rel_type_id(fc_rte->relid);
		if (!OidIsValid(fc_reltype))
			continue;
		fc_att->atttypid = fc_reltype;
		/* 不需要更改其他任何内容 */
	}
	return fc_tupdesc;
}

/*
 * postgresBeginForeignScan
 *		启动对外部 PostgreSQL 表的执行器扫描。
 */
static void fc_postgresBeginForeignScan(ForeignScanState *fc_node, int fc_eflags)
{
	ForeignScan *fc_fsplan = (ForeignScan *) fc_node->ss.ps.plan;
	EState	   *fc_estate = fc_node->ss.ps.state;
	PgFdwScanState *fc_fsstate;
	RangeTblEntry *fc_rte;
	Oid			fc_userid;
	ForeignTable *fc_table;
	UserMapping *fc_user;
	int			fc_rtindex;
	int			fc_numParams;

	/*
	 * 在 EXPLAIN（无 ANALYZE）情况下不执行任何操作。node->fdw_state 保持 NULL。
	 */
	if (fc_eflags & EXEC_FLAG_EXPLAIN_ONLY)
		return;

	/*
	 * 我们将把私有状态保存在 node->fdw_state 中。
	 */
	fc_fsstate = (PgFdwScanState *) palloc0(sizeof(PgFdwScanState));
	fc_node->fdw_state = (void *) fc_fsstate;

	/*
	 * 确定哪个用户进行远程访问。 这应该与
	 * ExecCheckRTEPerms() 的操作匹配。 在连接或聚合的情况下，
	 * 使用编号最低的成员 RTE 作为代表；我们从任何成员获得的结果都是相同的。
	 */
	if (fc_fsplan->scan.scanrelid > 0)
		fc_rtindex = fc_fsplan->scan.scanrelid;
	else
		fc_rtindex = bms_next_member(fc_fsplan->fs_relids, -1);
	fc_rte = exec_rt_fetch(fc_rtindex, fc_estate);
	fc_userid = fc_rte->checkAsUser ? fc_rte->checkAsUser : GetUserId();

	/* 获取关于外部表的信息。 */
	fc_table = GetForeignTable(fc_rte->relid);
	fc_user = GetUserMapping(fc_userid, fc_table->serverid);

	/*
	 * 获取与外部服务器的连接。 连接管理器将
	 * 在必要时建立新的连接。
	 */
	fc_fsstate->conn = GetConnection(fc_user, false, &fc_fsstate->conn_state);

	/* 为我的游标分配一个唯一 ID */
	fc_fsstate->cursor_number = GetCursorNumber(fc_fsstate->conn);
	fc_fsstate->cursor_exists = false;

	/* 获取由规划器函数创建的私有信息。 */
	fc_fsstate->query = strVal(list_nth(fc_fsplan->fdw_private,
									 FdwScanPrivateSelectSql));
	fc_fsstate->retrieved_attrs = (List *) list_nth(fc_fsplan->fdw_private,
												 FdwScanPrivateRetrievedAttrs);
	fc_fsstate->fetch_size = intVal(list_nth(fc_fsplan->fdw_private,
										  FdwScanPrivateFetchSize));

	/* 为元组批次和每个元组的临时工作区创建上下文。 */
	fc_fsstate->batch_cxt = AllocSetContextCreate(fc_estate->es_query_cxt,
											   "postgres_fdw tuple data",
											   ALLOCSET_DEFAULT_SIZES);
	fc_fsstate->temp_cxt = AllocSetContextCreate(fc_estate->es_query_cxt,
											  "postgres_fdw temporary data",
											  ALLOCSET_SMALL_SIZES);

	/*
	 * 获取转换从外部服务器获取的数据所需的信息
	 * 以便在该过程中进行本地表示和错误报告。
	 */
	if (fc_fsplan->scan.scanrelid > 0)
	{
		fc_fsstate->rel = fc_node->ss.ss_currentRelation;
		fc_fsstate->tupdesc = RelationGetDescr(fc_fsstate->rel);
	}
	else
	{
		fc_fsstate->rel = NULL;
		fc_fsstate->tupdesc = fc_get_tupdesc_for_join_scan_tuples(fc_node);
	}

	fc_fsstate->attinmeta = TupleDescGetAttInMetadata(fc_fsstate->tupdesc);

	/*
	 * 为远程查询中使用的参数处理做准备（如果有）。
	 */
	fc_numParams = list_length(fc_fsplan->fdw_exprs);
	fc_fsstate->numParams = fc_numParams;
	if (fc_numParams > 0)
		fc_prepare_query_params((PlanState *) fc_node,
							 fc_fsplan->fdw_exprs,
							 fc_numParams,
							 &fc_fsstate->param_flinfo,
							 &fc_fsstate->param_exprs,
							 &fc_fsstate->param_values);

	/* 设置异步能力标志 */
	fc_fsstate->async_capable = fc_node->ss.ps.async_capable;
}

/*
 * postgresIterateForeignScan
 *		从结果集中检索下一行，或清除元组槽以指示
 *		文件末尾（EOF）。
 */
static TupleTableSlot * fc_postgresIterateForeignScan(ForeignScanState *fc_node)
{
	PgFdwScanState *fc_fsstate = (PgFdwScanState *) fc_node->fdw_state;
	TupleTableSlot *fc_slot = fc_node->ss.ss_ScanTupleSlot;

	/*
	 * 在同步模式下，如果这是在 Begin 或 ReScan 后的第一次调用，我们需要
	 * 在远程端创建游标。在异步模式下，即使这是在 Begin 或 ReScan 后的
	 * 第一次调用，我们也会在到达此处之前已经创建了游标。
	 */
	if (!fc_fsstate->cursor_exists)
		fc_create_cursor(fc_node);

	/*
	 * 如果我们已经没有更多的元组，获取一些更多的元组。
	 */
	if (fc_fsstate->next_tuple >= fc_fsstate->num_tuples)
	{
		/* 在异步模式中，只需清除元组槽。 */
		if (fc_fsstate->async_capable)
			return ExecClearTuple(fc_slot);
		/* 如果我们已经检测到 EOF，就没有必要再进行另一次获取。 */
		if (!fc_fsstate->eof_reached)
			fc_fetch_more_data(fc_node);
		/* 如果我们没有获得任何元组，则必须是数据的结束。 */
		if (fc_fsstate->next_tuple >= fc_fsstate->num_tuples)
			return ExecClearTuple(fc_slot);
	}

	/*
	 * 返回下一个元组。
	 */
	ExecStoreHeapTuple(fc_fsstate->tuples[fc_fsstate->next_tuple++],
					   fc_slot,
					   false);

	return fc_slot;
}

/*
 * postgresReScanForeignScan
 *		重新启动扫描。
 */
static void fc_postgresReScanForeignScan(ForeignScanState *fc_node)
{
	PgFdwScanState *fc_fsstate = (PgFdwScanState *) fc_node->fdw_state;
	char		fc_sql[64];
	PGresult   *fc_res;

	/* 如果我们还没有创建游标，那就没什么好做的。 */
	if (!fc_fsstate->cursor_exists)
		return;

	/*
	 * 如果该节点是异步能力的，并且已经开始了异步获取，
	 * 则异步获取可能尚未完成。检查该节点是否是异步能力的，
	 * 并且对其的异步获取仍在进行中；如果是，则在重新启动扫描之前
	 * 完成异步获取。
	 */
	if (fc_fsstate->async_capable &&
		fc_fsstate->conn_state->pendingAreq &&
		fc_fsstate->conn_state->pendingAreq->requestee == (PlanState *) fc_node)
		fc_fetch_more_data(fc_node);

	/*
	 * 如果影响此节点的任何内部参数已更改，我们最好销毁并重新创建游标。
	 * 否则，如果远程服务器是 v14 或更旧版本，重新回绕应该足够；如果不是，
	 * 重新回绕仅允许用于可滚动游标，但我们没有办法检查其可滚动性，
	 * 所以无论如何都要销毁并重新创建。如果我们只获取了零个或一个批次，
	 * 我们甚至不需要重新回绕游标，只需重新扫描我们已有的内容。
	 */
	if (fc_node->ss.ps.chgParam != NULL)
	{
		fc_fsstate->cursor_exists = false;
		snprintf(fc_sql, sizeof(fc_sql), "CLOSE c%u",
				 fc_fsstate->cursor_number);
	}
	else if (fc_fsstate->fetch_ct_2 > 1)
	{
		if (PQserverVersion(fc_fsstate->conn) < 150000)
			snprintf(fc_sql, sizeof(fc_sql), "MOVE BACKWARD ALL IN c%u",
					 fc_fsstate->cursor_number);
		else
		{
			fc_fsstate->cursor_exists = false;
			snprintf(fc_sql, sizeof(fc_sql), "CLOSE c%u",
					 fc_fsstate->cursor_number);
		}
	}
	else
	{
		/* 简单：只需重新扫描我们已在内存中的内容（如果有） */
		fc_fsstate->next_tuple = 0;
		return;
	}

	/*
	 * 我们在这里不使用 PG_TRY 块，所以要小心不要在不释放 PGresult 的
	 * 情况下抛出错误。
	 */
	fc_res = pgfdw_exec_query(fc_fsstate->conn, fc_sql, fc_fsstate->conn_state);
	if (PQresultStatus(fc_res) != PGRES_COMMAND_OK)
		pgfdw_report_error(ERROR, fc_res, fc_fsstate->conn, true, fc_sql);
	PQclear(fc_res);

	/* 现在强制进行一次新的 FETCH。 */
	fc_fsstate->tuples = NULL;
	fc_fsstate->num_tuples = 0;
	fc_fsstate->next_tuple = 0;
	fc_fsstate->fetch_ct_2 = 0;
	fc_fsstate->eof_reached = false;
}

/*
 * postgresEndForeignScan
 *		完成对外部表的扫描并处理用于此扫描的对象
 */
static void fc_postgresEndForeignScan(ForeignScanState *fc_node)
{
	PgFdwScanState *fc_fsstate = (PgFdwScanState *) fc_node->fdw_state;

	/* 如果 fsstate 为 NULL，我们在 EXPLAIN；没什么好做的 */
	if (fc_fsstate == NULL)
		return;

	/* 如果游标打开，则关闭游标，以防止游标的积累 */
	if (fc_fsstate->cursor_exists)
		fc_close_cursor(fc_fsstate->conn, fc_fsstate->cursor_number,
					 fc_fsstate->conn_state);

	/* 释放远程连接 */
	ReleaseConnection(fc_fsstate->conn);
	fc_fsstate->conn = NULL;

	/* MemoryContexts 将会被自动删除。 */
}

/*
 * postgresAddForeignUpdateTargets
 *		添加在外部表上更新/删除所需的 resjunk 列
 */
static void fc_postgresAddForeignUpdateTargets(PlannerInfo *fc_root,
								Index fc_rtindex,
								RangeTblEntry *fc_target_rte,
								Relation fc_target_relation)
{
	Var		   *fc_var;

	/*
	 * 在 postgres_fdw 中，我们需要的是 ctid，和常规表相同。
	 */

	/* 创建一个表示所需值的 Var */
	fc_var = makeVar(fc_rtindex,
				  SelfItemPointerAttributeNumber,
				  TIDOID,
				  -1,
				  InvalidOid,
				  0);

	/* 将其注册为该目标关系所需的行标识列 */
	add_row_identity_var(fc_root, fc_var, fc_rtindex, "ctid");
}

/*
 * postgresPlanForeignModify
 *		计划对外部表的插入/更新/删除操作
 */
static List * fc_postgresPlanForeignModify(PlannerInfo *fc_root,
						  ModifyTable *fc_plan,
						  Index fc_resultRelation,
						  int fc_subplan_index)
{
	CmdType		fc_operation = fc_plan->operation;
	RangeTblEntry *fc_rte = planner_rt_fetch(fc_resultRelation, fc_root);
	Relation	fc_rel;
	StringInfoData fc_sql;
	List	   *fc_targetAttrs = NIL;
	List	   *fc_withCheckOptionList = NIL;
	List	   *fc_returningList = NIL;
	List	   *fc_retrieved_attrs = NIL;
	bool		fc_doNothing = false;
	int			fc_values_end_len = -1;

	initStringInfo(&fc_sql);

	/*
	 * 核心代码已经对正在计划的每个关系上加锁，因此我们可以在这里使用 NoLock。
	 */
	fc_rel = table_open(fc_rte->relid, NoLock);

	/*
	 * 在插入操作中，我们传输外部表中定义的所有列。在更新操作中，如果外部表上有 BEFORE ROW UPDATE 触发器，我们像插入一样传输所有列；否则我们只传输作为更新目标的列，以避免不必要的数据传输。（我们不能在插入时这样做，因为我们会错过发送源语句中未列出的列的默认值，对于更新，如果存在 BEFORE ROW UPDATE 触发器，因为这些触发器可能会更改非目标列的值，在这种情况下，我们会错过发送这些列的更改值。）
	 */
	if (fc_operation == CMD_INSERT ||
		(fc_operation == CMD_UPDATE &&
		 fc_rel->trigdesc &&
		 fc_rel->trigdesc->trig_update_before_row))
	{
		TupleDesc	fc_tupdesc = RelationGetDescr(fc_rel);
		int			fc_attnum;

		for (fc_attnum = 1; fc_attnum <= fc_tupdesc->natts; fc_attnum++)
		{
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_attnum - 1);

			if (!fc_attr->attisdropped)
				fc_targetAttrs = lappend_int(fc_targetAttrs, fc_attnum);
		}
	}
	else if (fc_operation == CMD_UPDATE)
	{
		int			fc_col;
		RelOptInfo *fc_rel = find_base_rel(fc_root, fc_resultRelation);
		Bitmapset  *fc_allUpdatedCols = get_rel_all_updated_cols(fc_root, fc_rel);

		fc_col = -1;
		while ((fc_col = bms_next_member(fc_allUpdatedCols, fc_col)) >= 0)
		{
			/* 位数由 FirstLowInvalidHeapAttributeNumber 偏移 */
			AttrNumber	fc_attno = fc_col + FirstLowInvalidHeapAttributeNumber;

			if (fc_attno <= InvalidAttrNumber) /* 不应该发生 */
				elog(ERROR, "system-column update is not supported");
			fc_targetAttrs = lappend_int(fc_targetAttrs, fc_attno);
		}
	}

	/*
	 * 提取相关的 WITH CHECK OPTION 列表（如果有）。
	 */
	if (fc_plan->withCheckOptionLists)
		fc_withCheckOptionList = (List *) list_nth(fc_plan->withCheckOptionLists,
												fc_subplan_index);

	/*
	 * 提取相关的 RETURNING 列表（如果有）。
	 */
	if (fc_plan->returningLists)
		fc_returningList = (List *) list_nth(fc_plan->returningLists, fc_subplan_index);

	/*
	 * ON CONFLICT DO UPDATE 和 DO NOTHING 的情况及推断规格应在优化器中被拒绝，因为目前没有办法识别外部表上的仲裁索引。仅支持没有推断规格的 DO NOTHING。
	 */
	if (fc_plan->onConflictAction == ONCONFLICT_NOTHING)
		fc_doNothing = true;
	else if (fc_plan->onConflictAction != ONCONFLICT_NONE)
		elog(ERROR, "unexpected ON CONFLICT specification: %d",
			 (int) fc_plan->onConflictAction);

	/*
	 * 构建 SQL 命令字符串。
	 */
	switch (fc_operation)
	{
		case CMD_INSERT:
			deparseInsertSql(&fc_sql, fc_rte, fc_resultRelation, fc_rel,
							 fc_targetAttrs, fc_doNothing,
							 fc_withCheckOptionList, fc_returningList,
							 &fc_retrieved_attrs, &fc_values_end_len);
			break;
		case CMD_UPDATE:
			deparseUpdateSql(&fc_sql, fc_rte, fc_resultRelation, fc_rel,
							 fc_targetAttrs,
							 fc_withCheckOptionList, fc_returningList,
							 &fc_retrieved_attrs);
			break;
		case CMD_DELETE:
			deparseDeleteSql(&fc_sql, fc_rte, fc_resultRelation, fc_rel,
							 fc_returningList,
							 &fc_retrieved_attrs);
			break;
		default:
			elog(ERROR, "unexpected operation: %d", (int) fc_operation);
			break;
	}

	table_close(fc_rel, NoLock);

	/*
	 * 构建将可用于执行器的 fdw_private 列表。列表中的项必须与上面的 enum FdwModifyPrivateIndex 匹配。
	 */
	return list_make5(makeString(fc_sql.data),
					  fc_targetAttrs,
					  makeInteger(fc_values_end_len),
					  makeBoolean((fc_retrieved_attrs != NIL)),
					  fc_retrieved_attrs);
}

/*
 * postgresBeginForeignModify
 *		开始对外部表进行插入/更新/删除操作
 */
static void fc_postgresBeginForeignModify(ModifyTableState *fc_mtstate,
						   ResultRelInfo *fc_resultRelInfo,
						   List *fc_fdw_private,
						   int fc_subplan_index,
						   int fc_eflags)
{
	PgFdwModifyState *fc_fmstate;
	char	   *fc_query;
	List	   *fc_target_attrs;
	bool		fc_has_returning;
	int			fc_values_end_len;
	List	   *fc_retrieved_attrs;
	RangeTblEntry *fc_rte;

	/*
	 * 在 EXPLAIN（无 ANALYZE）情况下不执行任何操作。resultRelInfo->ri_FdwState 保持 NULL。
	 */
	if (fc_eflags & EXEC_FLAG_EXPLAIN_ONLY)
		return;

	/* 解构 fdw_private 数据。 */
	fc_query = strVal(list_nth(fc_fdw_private,
							FdwModifyPrivateUpdateSql));
	fc_target_attrs = (List *) list_nth(fc_fdw_private,
									 FdwModifyPrivateTargetAttnums);
	fc_values_end_len = intVal(list_nth(fc_fdw_private,
									 FdwModifyPrivateLen));
	fc_has_returning = boolVal(list_nth(fc_fdw_private,
									 FdwModifyPrivateHasReturning));
	fc_retrieved_attrs = (List *) list_nth(fc_fdw_private,
										FdwModifyPrivateRetrievedAttrs);

	/* 查找 RTE。 */
	fc_rte = exec_rt_fetch(fc_resultRelInfo->ri_RangeTableIndex,
						fc_mtstate->ps.state);

	/* 构建执行状态。 */
	fc_fmstate = fc_create_foreign_modify(fc_mtstate->ps.state,
									fc_rte,
									fc_resultRelInfo,
									fc_mtstate->operation,
									outerPlanState(fc_mtstate)->plan,
									fc_query,
									fc_target_attrs,
									fc_values_end_len,
									fc_has_returning,
									fc_retrieved_attrs);

	fc_resultRelInfo->ri_FdwState = fc_fmstate;
}

/*
 * postgresExecForeignInsert
 *		向外部表插入一行
 */
static TupleTableSlot * fc_postgresExecForeignInsert(EState *fc_estate,
						  ResultRelInfo *fc_resultRelInfo,
						  TupleTableSlot *fc_slot,
						  TupleTableSlot *fc_planSlot)
{
	PgFdwModifyState *fc_fmstate = (PgFdwModifyState *) fc_resultRelInfo->ri_FdwState;
	TupleTableSlot **fc_rslot;
	int			fc_numSlots = 1;

	/*
	 * 如果 fmstate 设置了 aux_fmstate，则使用 aux_fmstate（见 postgresBeginForeignInsert()）
	 */
	if (fc_fmstate->aux_fmstate)
		fc_resultRelInfo->ri_FdwState = fc_fmstate->aux_fmstate;
	fc_rslot = fc_execute_foreign_modify(fc_estate, fc_resultRelInfo, CMD_INSERT,
								   &fc_slot, &fc_planSlot, &fc_numSlots);
	/* 撤销该更改 */
	if (fc_fmstate->aux_fmstate)
		fc_resultRelInfo->ri_FdwState = fc_fmstate;

	return fc_rslot ? *fc_rslot : NULL;
}

/*
 * postgresExecForeignBatchInsert
 *		向外部表插入多行
 */
static TupleTableSlot **
fc_postgresExecForeignBatchInsert(EState *fc_estate,
							   ResultRelInfo *fc_resultRelInfo,
							   TupleTableSlot **fc_slots,
							   TupleTableSlot **fc_planSlots,
							   int *fc_numSlots)
{
	PgFdwModifyState *fc_fmstate = (PgFdwModifyState *) fc_resultRelInfo->ri_FdwState;
	TupleTableSlot **fc_rslot;

	/*
	 * 如果 fmstate 设置了 aux_fmstate，则使用 aux_fmstate（见 postgresBeginForeignInsert()）
	 */
	if (fc_fmstate->aux_fmstate)
		fc_resultRelInfo->ri_FdwState = fc_fmstate->aux_fmstate;
	fc_rslot = fc_execute_foreign_modify(fc_estate, fc_resultRelInfo, CMD_INSERT,
								   fc_slots, fc_planSlots, fc_numSlots);
	/* 撤销该更改 */
	if (fc_fmstate->aux_fmstate)
		fc_resultRelInfo->ri_FdwState = fc_fmstate;

	return fc_rslot;
}

/*
 * postgresGetForeignModifyBatchSize
 *		确定可以批量插入的最大元组数
 *
 * 返回为服务器或表指定的批量大小。当不允许批量处理时（例如，对于具有 BEFORE/AFTER ROW 触发器或带有 RETURNING 子句的表），返回 1。
 */
static int fc_postgresGetForeignModifyBatchSize(ResultRelInfo *fc_resultRelInfo)
{
	int			fc_batch_size;
	PgFdwModifyState *fc_fmstate = fc_resultRelInfo->ri_FdwState ?
	(PgFdwModifyState *) fc_resultRelInfo->ri_FdwState :
	NULL;

	/* 应仅调用一次 */
	Assert(fc_resultRelInfo->ri_BatchSize == 0);

	/*
	 * 当插入作为行移动操作的一部分进行时不应被调用。
	 */
	Assert(fc_fmstate == NULL || fc_fmstate->aux_fmstate == NULL);

	/*
	 * 在没有 ANALYZE 的 EXPLAIN 中，ri_FdwState 为 NULL，因此我们必须直接在服务器/表选项中查找该选项。否则，仅使用我们之前确定的值。
	 */
	if (fc_fmstate)
		fc_batch_size = fc_fmstate->batch_size;
	else
		fc_batch_size = fc_get_batch_size_option(fc_resultRelInfo->ri_RelationDesc);

	/*
	 * 当我们必须使用 RETURNING 时，禁用批处理，如果外部表上有任何
	 * BEFORE/AFTER ROW INSERT 触发器，或者父视图中有任何
	 * WITH CHECK OPTION 约束。
	 *
	 * 当表上有任何 BEFORE ROW INSERT 触发器时，我们无法
	 * 支持它，因为此类触发器可能查询我们正在插入的表，
	 * 并且如果已经处理并准备好插入的元组不在那里，则行为会有所不同。
	 */
	if (fc_resultRelInfo->ri_projectReturning != NULL ||
		fc_resultRelInfo->ri_WithCheckOptions != NIL ||
		(fc_resultRelInfo->ri_TrigDesc &&
		 (fc_resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
		  fc_resultRelInfo->ri_TrigDesc->trig_insert_after_row)))
		return 1;

	/*
	 * 否则使用为服务器/表指定的批处理大小。批处理中的参数
	 * 数量限制为 65535（uint16），因此确保我们
	 * 不超过此限制，使用最大的 batch_size。
	 */
	if (fc_fmstate && fc_fmstate->p_nums > 0)
		fc_batch_size = Min(fc_batch_size, PQ_QUERY_PARAM_MAX_LIMIT / fc_fmstate->p_nums);

	return fc_batch_size;
}

/*
 * postgresExecForeignUpdate
 *		更新外部表中的一行
 */
static TupleTableSlot * fc_postgresExecForeignUpdate(EState *fc_estate,
						  ResultRelInfo *fc_resultRelInfo,
						  TupleTableSlot *fc_slot,
						  TupleTableSlot *fc_planSlot)
{
	TupleTableSlot **fc_rslot;
	int			fc_numSlots = 1;

	fc_rslot = fc_execute_foreign_modify(fc_estate, fc_resultRelInfo, CMD_UPDATE,
								   &fc_slot, &fc_planSlot, &fc_numSlots);

	return fc_rslot ? fc_rslot[0] : NULL;
}

/*
 * postgresExecForeignDelete
 *		从外部表中删除一行
 */
static TupleTableSlot * fc_postgresExecForeignDelete(EState *fc_estate,
						  ResultRelInfo *fc_resultRelInfo,
						  TupleTableSlot *fc_slot,
						  TupleTableSlot *fc_planSlot)
{
	TupleTableSlot **fc_rslot;
	int			fc_numSlots = 1;

	fc_rslot = fc_execute_foreign_modify(fc_estate, fc_resultRelInfo, CMD_DELETE,
								   &fc_slot, &fc_planSlot, &fc_numSlots);

	return fc_rslot ? fc_rslot[0] : NULL;
}

/*
 * postgresEndForeignModify
 *		结束在外部表上的插入/更新/删除操作
 */
static void fc_postgresEndForeignModify(EState *fc_estate,
						 ResultRelInfo *fc_resultRelInfo)
{
	PgFdwModifyState *fc_fmstate = (PgFdwModifyState *) fc_resultRelInfo->ri_FdwState;

	/* 如果 fmstate 为 NULL，我们处于 EXPLAIN；没有什么可做的 */
	if (fc_fmstate == NULL)
		return;

	/* 销毁执行状态 */
	fc_finish_foreign_modify(fc_fmstate);
}

/*
 * postgresBeginForeignInsert
 *		开始在外部表上插入操作
 */
static void fc_postgresBeginForeignInsert(ModifyTableState *fc_mtstate,
						   ResultRelInfo *fc_resultRelInfo)
{
	PgFdwModifyState *fc_fmstate;
	ModifyTable *fc_plan = castNode(ModifyTable, fc_mtstate->ps.plan);
	EState	   *fc_estate = fc_mtstate->ps.state;
	Index		fc_resultRelation;
	Relation	fc_rel = fc_resultRelInfo->ri_RelationDesc;
	RangeTblEntry *fc_rte;
	TupleDesc	fc_tupdesc = RelationGetDescr(fc_rel);
	int			fc_attnum;
	int			fc_values_end_len;
	StringInfoData fc_sql;
	List	   *fc_targetAttrs = NIL;
	List	   *fc_retrieved_attrs = NIL;
	bool		fc_doNothing = false;

	/*
	 * 如果我们即将插入的外部表也是将来会更新的
	 * UPDATE 子计划结果关系，继续执行
	 * INSERT 将导致后续 UPDATE 不正确地修改那些
	 * 路由的行，因此阻止 INSERT---如果我们能
	 * 处理这种情况那就好了；但现在，为了安全起见，抛出一个错误。
	 */
	if (fc_plan && fc_plan->operation == CMD_UPDATE &&
		(fc_resultRelInfo->ri_usesFdwDirectModify ||
		 fc_resultRelInfo->ri_FdwState))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot route tuples into foreign table to be updated \"%s\"",
						RelationGetRelationName(fc_rel))));

	initStringInfo(&fc_sql);

	/* 我们传输所有在外部表中定义的列。 */
	for (fc_attnum = 1; fc_attnum <= fc_tupdesc->natts; fc_attnum++)
	{
		Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_attnum - 1);

		if (!fc_attr->attisdropped)
			fc_targetAttrs = lappend_int(fc_targetAttrs, fc_attnum);
	}

	/* 检查我们是否将 ON CONFLICT 子句添加到远程查询。 */
	if (fc_plan)
	{
		OnConflictAction fc_onConflictAction = fc_plan->onConflictAction;

		/* 我们仅支持没有推断规范的 DO NOTHING。 */
		if (fc_onConflictAction == ONCONFLICT_NOTHING)
			fc_doNothing = true;
		else if (fc_onConflictAction != ONCONFLICT_NONE)
			elog(ERROR, "unexpected ON CONFLICT specification: %d",
				 (int) fc_onConflictAction);
	}

	/*
	 * 如果外部表是一个没有对应
	 * RTE 条目的分区，我们需要创建一个新的 RTE 描述外部表，
	 * 供 deparseInsertSql 和下面的 create_foreign_modify() 使用，
	 * 首先复制父级的 RTE 并修改一些字段以描述
	 * 要操作的外部分区。然而，如果这是由 UPDATE 调用的，
	 * 如果现有的 RTE 已经对应于此分区，如果它是
	 * UPDATE 子计划目标关系之一；在这种情况下，我们可以直接使用
	 * 现有的 RTE。
	 */
	if (fc_resultRelInfo->ri_RangeTableIndex == 0)
	{
		ResultRelInfo *fc_rootResultRelInfo = fc_resultRelInfo->ri_RootResultRelInfo;

		fc_rte = exec_rt_fetch(fc_rootResultRelInfo->ri_RangeTableIndex, fc_estate);
		fc_rte = copyObject(fc_rte);
		fc_rte->relid = RelationGetRelid(fc_rel);
		fc_rte->relkind = RELKIND_FOREIGN_TABLE;

		/*
		 * 对于 UPDATE，我们必须使用第一个子计划目标
		 * 关系的 RTE 的 RT 索引，因为核心代码会为
		 * 分区构建表达式，例如 RETURNING，使用该 RT 索引作为
		 * 这些表达式中包含的变量的 varno。
		 */
		if (fc_plan && fc_plan->operation == CMD_UPDATE &&
			fc_rootResultRelInfo->ri_RangeTableIndex == fc_plan->rootRelation)
			fc_resultRelation = fc_mtstate->resultRelInfo[0].ri_RangeTableIndex;
		else
			fc_resultRelation = fc_rootResultRelInfo->ri_RangeTableIndex;
	}
	else
	{
		fc_resultRelation = fc_resultRelInfo->ri_RangeTableIndex;
		fc_rte = exec_rt_fetch(fc_resultRelation, fc_estate);
	}

	/* 构建 SQL 命令字符串。 */
	deparseInsertSql(&fc_sql, fc_rte, fc_resultRelation, fc_rel, fc_targetAttrs, fc_doNothing,
					 fc_resultRelInfo->ri_WithCheckOptions,
					 fc_resultRelInfo->ri_returningList,
					 &fc_retrieved_attrs, &fc_values_end_len);

	/* 构建执行状态。 */
	fc_fmstate = fc_create_foreign_modify(fc_mtstate->ps.state,
									fc_rte,
									fc_resultRelInfo,
									CMD_INSERT,
									NULL,
									fc_sql.data,
									fc_targetAttrs,
									fc_values_end_len,
									fc_retrieved_attrs != NIL,
									fc_retrieved_attrs);

	/*
	 * 如果给定的 resultRelInfo 已经设置了 PgFdwModifyState，
	 * 这意味着外部表是一个 UPDATE 子计划结果关系；
	 * 在这种情况下，将结果状态存储到 PgFdwModifyState 的 aux_fmstate 中。
	 */
	if (fc_resultRelInfo->ri_FdwState)
	{
		Assert(fc_plan && fc_plan->operation == CMD_UPDATE);
		Assert(fc_resultRelInfo->ri_usesFdwDirectModify == false);
		((PgFdwModifyState *) fc_resultRelInfo->ri_FdwState)->aux_fmstate = fc_fmstate;
	}
	else
		fc_resultRelInfo->ri_FdwState = fc_fmstate;
}

/*
 * postgresEndForeignInsert
 *		结束在外部表上的插入操作
 */
static void fc_postgresEndForeignInsert(EState *fc_estate,
						 ResultRelInfo *fc_resultRelInfo)
{
	PgFdwModifyState *fc_fmstate = (PgFdwModifyState *) fc_resultRelInfo->ri_FdwState;

	Assert(fc_fmstate != NULL);

	/*
	 * 如果fmstate设置了aux_fmstate，则获取aux_fmstate（见
	 * postgresBeginForeignInsert()）
	 */
	if (fc_fmstate->aux_fmstate)
		fc_fmstate = fc_fmstate->aux_fmstate;

	/* 销毁执行状态 */
	fc_finish_foreign_modify(fc_fmstate);
}

/*
 * postgresIsForeignRelUpdatable
 *		确定一个外部表是否支持INSERT、UPDATE和/或
 *		DELETE。
 */
static int fc_postgresIsForeignRelUpdatable(Relation fc_rel)
{
	bool		fc_updatable;
	ForeignTable *fc_table;
	ForeignServer *fc_server;
	ListCell   *fc_lc;

	/*
	 * 默认情况下，所有postgres_fdw外部表都被认为是可更新的。这
	 * 可以通过每个服务器的设置覆盖，而此设置又可以通过每个表的设置
	 * 被覆盖。
	 */
	fc_updatable = true;

	fc_table = GetForeignTable(RelationGetRelid(fc_rel));
	fc_server = GetForeignServer(fc_table->serverid);

	foreach(fc_lc, fc_server->options)
	{
		DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

		if (strcmp(fc_def->defname, "updatable") == 0)
			fc_updatable = defGetBoolean(fc_def);
	}
	foreach(fc_lc, fc_table->options)
	{
		DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

		if (strcmp(fc_def->defname, "updatable") == 0)
			fc_updatable = defGetBoolean(fc_def);
	}

	/*
	 * 目前“可更新”意味着支持INSERT、UPDATE和DELETE。
	 */
	return fc_updatable ?
		(1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE) : 0;
}

/*
 * postgresRecheckForeignScan
 *		为外部连接执行本地连接执行计划
 */
static bool fc_postgresRecheckForeignScan(ForeignScanState *fc_node, TupleTableSlot *fc_slot)
{
	Index		fc_scanrelid = ((Scan *) fc_node->ss.ps.plan)->scanrelid;
	PlanState  *fc_outerPlan = outerPlanState(fc_node);
	TupleTableSlot *fc_result;

	/* 对于基本外部关系，仅需设置fdw_recheck_quals */
	if (fc_scanrelid > 0)
		return true;

	Assert(fc_outerPlan != NULL);

	/* 执行本地连接执行计划 */
	fc_result = ExecProcNode(fc_outerPlan);
	if (TupIsNull(fc_result))
		return false;

	/* 将结果存储在给定槽中 */
	ExecCopySlot(fc_slot, fc_result);

	return true;
}

/*
 * find_modifytable_subplan
 *		postgresPlanDirectModify的辅助例程，用于查找
 *		扫描指定RTI的ModifyTable子计划节点。
 *
 * 如果无法识别子计划，则返回NULL。这不是致命错误，
 * 我们只是放弃尝试直接进行更新。
 */
static ForeignScan * fc_find_modifytable_subplan(PlannerInfo *fc_root,
						 ModifyTable *fc_plan,
						 Index fc_rtindex,
						 int fc_subplan_index)
{
	Plan	   *fc_subplan = outerPlan(fc_plan);

	/*
	 * 我们支持的情况是（1）所需的ForeignScan是ModifyTable的直接
	 * 子节点，或（2）它是Append节点的subplan_index'th子节点，该
	 * 节点是ModifyTable的直接子节点。进一步查找没有意义，
	 * 因为这将意味着涉及本地连接，因此我们不能直接进行更新。
	 *
	 * Append上可能还有一个Result，用于计算UPDATE目标列表的值。
	 * 我们在这里忽略这一点；目标列表将由我们的调用者检查。
	 *
	 * 原则上，我们可以检查Append的所有子节点，但目前不太可能
	 * 核心规划器会生成具有无序子节点的计划。此外，这种搜索在有
	 * 很多子节点时风险成本为O(N^2)时间。
	 */
	if (IsA(fc_subplan, Append))
	{
		Append	   *fc_appendplan = (Append *) fc_subplan;

		if (fc_subplan_index < list_length(fc_appendplan->appendplans))
			fc_subplan = (Plan *) list_nth(fc_appendplan->appendplans, fc_subplan_index);
	}
	else if (IsA(fc_subplan, Result) &&
			 outerPlan(fc_subplan) != NULL &&
			 IsA(outerPlan(fc_subplan), Append))
	{
		Append	   *fc_appendplan = (Append *) outerPlan(fc_subplan);

		if (fc_subplan_index < list_length(fc_appendplan->appendplans))
			fc_subplan = (Plan *) list_nth(fc_appendplan->appendplans, fc_subplan_index);
	}

	/* 那么，我们是否得到了所需关系上的ForeignScan？ */
	if (IsA(fc_subplan, ForeignScan))
	{
		ForeignScan *fc_fscan = (ForeignScan *) fc_subplan;

		if (bms_is_member(fc_rtindex, fc_fscan->fs_relids))
			return fc_fscan;
	}

	return NULL;
}

/*
 * postgresPlanDirectModify
 *		考虑直接修改外部表
 *
 * 决定是否安全地直接修改外部表，如果可以，
 * 则相应地重写子计划。
 */
static bool fc_postgresPlanDirectModify(PlannerInfo *fc_root,
						 ModifyTable *fc_plan,
						 Index fc_resultRelation,
						 int fc_subplan_index)
{
	CmdType		fc_operation = fc_plan->operation;
	RelOptInfo *fc_foreignrel;
	RangeTblEntry *fc_rte;
	PgFdwRelationInfo *fc_fpinfo;
	Relation	fc_rel;
	StringInfoData fc_sql;
	ForeignScan *fc_fscan;
	List	   *fc_processed_tlist = NIL;
	List	   *fc_targetAttrs = NIL;
	List	   *fc_remote_exprs;
	List	   *fc_params_list = NIL;
	List	   *fc_returningList = NIL;
	List	   *fc_retrieved_attrs = NIL;

	/*
	 * 决定是否安全地直接修改外部表。
	 */

	/*
	 * 表修改必须是UPDATE或DELETE。
	 */
	if (fc_operation != CMD_UPDATE && fc_operation != CMD_DELETE)
		return false;

	/*
	 * 尝试定位正在扫描resultRelation的ForeignScan子计划。
	 */
	fc_fscan = fc_find_modifytable_subplan(fc_root, fc_plan, fc_resultRelation, fc_subplan_index);
	if (!fc_fscan)
		return false;

	/*
	 * 如果有任何应在本地评估的条件，则直接修改外部表是不安全的。
	 */
	if (fc_fscan->scan.plan.qual != NIL)
		return false;

	/* 安全获取有关目标外部关系的数据 */
	if (fc_fscan->scan.scanrelid == 0)
	{
		fc_foreignrel = find_join_rel(fc_root, fc_fscan->fs_relids);
		/* 我们应该有一个与此外部连接相关的关系。 */
		Assert(fc_foreignrel);
	}
	else
		fc_foreignrel = fc_root->simple_rel_array[fc_resultRelation];
	fc_rte = fc_root->simple_rte_array[fc_resultRelation];
	fc_fpinfo = (PgFdwRelationInfo *) fc_foreignrel->fdw_private;

	/*
	 * 如果任何分配给目标列的表达式在远程评估时不安全，
	 * 则直接更新外部表是不安全的。
	 */
	if (fc_operation == CMD_UPDATE)
	{
		ListCell   *fc_lc,
				   *fc_lc2;

		/*
		 * 关注的表达式是处理后的目标列表的前N列，其中N是
		 * 关系的update_colnos的长度。
		 */
		get_translated_update_targetlist(fc_root, fc_resultRelation,
										 &fc_processed_tlist, &fc_targetAttrs);
		forboth(fc_lc, fc_processed_tlist, fc_lc2, fc_targetAttrs)
		{
			TargetEntry *fc_tle = lfirst_node(TargetEntry, fc_lc);
			AttrNumber	fc_attno = lfirst_int(fc_lc2);

			/*
 * 反解析远程 DELETE 语句
 *
 * 语句文本附加到 buf，我们还创建一个整数列表
 * 表示通过 RETURNING 获取的列（如果有的话），该列表返回
 * 给 *retrieved_attrs。
 */
			Assert(!fc_tle->resjunk);

			if (fc_attno <= InvalidAttrNumber) /* 不应该发生 */
				elog(ERROR, "system-column update is not supported");

			if (!is_foreign_expr(fc_root, fc_foreignrel, (Expr *) fc_tle->expr))
				return false;
		}
	}

	/*
	 * 好吧，重写子计划以直接修改外部表。
	 */
	initStringInfo(&fc_sql);

	/*
	 * 核心代码已经对正在计划的每个关系上加锁，因此我们可以在这里使用 NoLock。
	 */
	fc_rel = table_open(fc_rte->relid, NoLock);

	/*
	 * 回忆一下必须远程评估的条件子句。 （这些是
	 * 原始子句而不是 RestrictInfos，但 deparse.c 的 appendConditions()
	 * 不在乎。）
	 */
	fc_remote_exprs = fc_fpinfo->final_remote_exprs;

	/*
	 * 提取相关的 RETURNING 列表（如果有）。
	 */
	if (fc_plan->returningLists)
	{
		fc_returningList = (List *) list_nth(fc_plan->returningLists, fc_subplan_index);

		/*
		 * 当在连接上直接执行 UPDATE/DELETE .. RETURNING 时，
		 * 我们从外部服务器获取在 RETURNING 中指定的任何 Vars
		 * ，它们不仅引用目标关系，还引用非目标
		 * 关系。因此，我们将它们反解析到远程查询的 RETURNING 子句中；而是使用一个由它们组成的目标列表，
		 * 将调整为下面外部扫描计划节点的新的 fdw_scan_tlist。
		 */
		if (fc_fscan->scan.scanrelid == 0)
			fc_returningList = fc_build_remote_returning(fc_resultRelation, fc_rel,
												   fc_returningList);
	}

	/*
	 * 构建 SQL 命令字符串。
	 */
	switch (fc_operation)
	{
		case CMD_UPDATE:
			deparseDirectUpdateSql(&fc_sql, fc_root, fc_resultRelation, fc_rel,
								   fc_foreignrel,
								   fc_processed_tlist,
								   fc_targetAttrs,
								   fc_remote_exprs, &fc_params_list,
								   fc_returningList, &fc_retrieved_attrs);
			break;
		case CMD_DELETE:
			deparseDirectDeleteSql(&fc_sql, fc_root, fc_resultRelation, fc_rel,
								   fc_foreignrel,
								   fc_remote_exprs, &fc_params_list,
								   fc_returningList, &fc_retrieved_attrs);
			break;
		default:
			elog(ERROR, "unexpected operation: %d", (int) fc_operation);
			break;
	}

	/*
	 * 更新操作和目标关系信息。
	 */
	fc_fscan->operation = fc_operation;
	fc_fscan->resultRelation = fc_resultRelation;

	/*
	 * 更新将可供执行器使用的 fdw_exprs 列表。
	 */
	fc_fscan->fdw_exprs = fc_params_list;

	/*
	 * 更新将可供执行器使用的 fdw_private 列表。
	 * 列表中的项必须与上面的枚举 FdwDirectModifyPrivateIndex 匹配。
	 */
	fc_fscan->fdw_private = list_make4(makeString(fc_sql.data),
									makeBoolean((fc_retrieved_attrs != NIL)),
									fc_retrieved_attrs,
									makeBoolean(fc_plan->canSetTag));

	/*
	 * 更新与外部连接相关的字段。
	 */
	if (fc_fscan->scan.scanrelid == 0)
	{
		/* 不需要外部子计划。 */
		fc_fscan->scan.plan.lefttree = NULL;

		/* 如果是 UPDATE/DELETE .. RETURNING，则构建新的 fdw_scan_tlist。 */
		if (fc_returningList)
			fc_rebuild_fdw_scan_tlist(fc_fscan, fc_returningList);
	}

	/*
	 * 最后，如果已设置 async-capable 标志，则取消设置它，因为我们目前
	 * 不支持直接修改的异步执行。
	 */
	if (fc_fscan->scan.plan.async_capable)
		fc_fscan->scan.plan.async_capable = false;

	table_close(fc_rel, NoLock);
	return true;
}

/*
 * postgresBeginDirectModify
 *		准备进行直接的外部表修改
 */
static void fc_postgresBeginDirectModify(ForeignScanState *fc_node, int fc_eflags)
{
	ForeignScan *fc_fsplan = (ForeignScan *) fc_node->ss.ps.plan;
	EState	   *fc_estate = fc_node->ss.ps.state;
	PgFdwDirectModifyState *fc_dmstate;
	Index		fc_rtindex;
	RangeTblEntry *fc_rte;
	Oid			fc_userid;
	ForeignTable *fc_table;
	UserMapping *fc_user;
	int			fc_numParams;

	/*
	 * 在 EXPLAIN（无 ANALYZE）情况下不执行任何操作。node->fdw_state 保持 NULL。
	 */
	if (fc_eflags & EXEC_FLAG_EXPLAIN_ONLY)
		return;

	/*
	 * 我们将把私有状态保存在 node->fdw_state 中。
	 */
	fc_dmstate = (PgFdwDirectModifyState *) palloc0(sizeof(PgFdwDirectModifyState));
	fc_node->fdw_state = (void *) fc_dmstate;

	/*
	 * 确定作为哪个用户进行远程访问。这应与
	 * ExecCheckRTEPerms() 的操作相匹配。
	 */
	fc_rtindex = fc_node->resultRelInfo->ri_RangeTableIndex;
	fc_rte = exec_rt_fetch(fc_rtindex, fc_estate);
	fc_userid = fc_rte->checkAsUser ? fc_rte->checkAsUser : GetUserId();

	/* 获取关于外部表的信息。 */
	if (fc_fsplan->scan.scanrelid == 0)
		fc_dmstate->rel = ExecOpenScanRelation(fc_estate, fc_rtindex, fc_eflags);
	else
		fc_dmstate->rel = fc_node->ss.ss_currentRelation;
	fc_table = GetForeignTable(RelationGetRelid(fc_dmstate->rel));
	fc_user = GetUserMapping(fc_userid, fc_table->serverid);

	/*
	 * 获取与外部服务器的连接。 连接管理器将
	 * 在必要时建立新的连接。
	 */
	fc_dmstate->conn = GetConnection(fc_user, false, &fc_dmstate->conn_state);

	/* 更新与外部连接相关的字段。 */
	if (fc_fsplan->scan.scanrelid == 0)
	{
		/* 保存关于外部表的信息。 */
		fc_dmstate->resultRel = fc_dmstate->rel;

		/*
		 * 将 dmstate->rel 设置为 NULL，以便告诉 get_returning_data() 和
		 * make_tuple_from_result_row() 从远程服务器提取的列是由
		 * 外部扫描计划节点的 fdw_scan_tlist 描述的，而不是目标关系的元组描述符。
		 */
		fc_dmstate->rel = NULL;
	}

	/* 初始化状态变量 */
	fc_dmstate->num_tuples = -1;	/* -1 意味着尚未设置 */

	/* 获取由规划器函数创建的私有信息。 */
	fc_dmstate->query = strVal(list_nth(fc_fsplan->fdw_private,
									 FdwDirectModifyPrivateUpdateSql));
	fc_dmstate->has_returning = boolVal(list_nth(fc_fsplan->fdw_private,
											  FdwDirectModifyPrivateHasReturning));
	fc_dmstate->retrieved_attrs = (List *) list_nth(fc_fsplan->fdw_private,
												 FdwDirectModifyPrivateRetrievedAttrs);
	fc_dmstate->set_processed = boolVal(list_nth(fc_fsplan->fdw_private,
											  FdwDirectModifyPrivateSetProcessed));

	/* 为每个元组创建临时工作区的上下文。 */
	fc_dmstate->temp_cxt = AllocSetContextCreate(fc_estate->es_query_cxt,
											  "postgres_fdw temporary data",
											  ALLOCSET_SMALL_SIZES);

	/* 为 RETURNING 结果的输入转换做准备。 */
	if (fc_dmstate->has_returning)
	{
		TupleDesc	fc_tupdesc;

		if (fc_fsplan->scan.scanrelid == 0)
			fc_tupdesc = fc_get_tupdesc_for_join_scan_tuples(fc_node);
		else
			fc_tupdesc = RelationGetDescr(fc_dmstate->rel);

		fc_dmstate->attinmeta = TupleDescGetAttInMetadata(fc_tupdesc);

		/*
		 * 当在连接上直接执行 UPDATE/DELETE .. RETURNING 时，
		 * 初始化一个过滤器以从扫描元组中提取更新/删除的元组。
		 */
		if (fc_fsplan->scan.scanrelid == 0)
			fc_init_returning_filter(fc_dmstate, fc_fsplan->fdw_scan_tlist, fc_rtindex);
	}

	/*
	 * 为远程查询中使用的参数处理做准备（如果有）。
	 */
	fc_numParams = list_length(fc_fsplan->fdw_exprs);
	fc_dmstate->numParams = fc_numParams;
	if (fc_numParams > 0)
		fc_prepare_query_params((PlanState *) fc_node,
							 fc_fsplan->fdw_exprs,
							 fc_numParams,
							 &fc_dmstate->param_flinfo,
							 &fc_dmstate->param_exprs,
							 &fc_dmstate->param_values);
}

/*
 * postgresIterateDirectModify
 *		执行直接外部表的修改
 */
static TupleTableSlot * fc_postgresIterateDirectModify(ForeignScanState *fc_node)
{
	PgFdwDirectModifyState *fc_dmstate = (PgFdwDirectModifyState *) fc_node->fdw_state;
	EState	   *fc_estate = fc_node->ss.ps.state;
	ResultRelInfo *fc_resultRelInfo = fc_node->resultRelInfo;

	/*
	 * 如果这是 Begin 之后的第一次调用，执行该语句。
	 */
	if (fc_dmstate->num_tuples == -1)
		fc_execute_dml_stmt(fc_node);

	/*
	 * 如果本地查询未指定 RETURNING，只需清除元组槽。
	 */
	if (!fc_resultRelInfo->ri_projectReturning)
	{
		TupleTableSlot *fc_slot = fc_node->ss.ss_ScanTupleSlot;
		Instrumentation *fc_instr = fc_node->ss.ps.instrument;

		Assert(!fc_dmstate->has_returning);

		/* 如果需要，增加命令 es_processed 计数。 */
		if (fc_dmstate->set_processed)
			fc_estate->es_processed += fc_dmstate->num_tuples;

		/* 如果需要，增加 EXPLAIN ANALYZE 的元组计数。 */
		if (fc_instr)
			fc_instr->tuplecount += fc_dmstate->num_tuples;

		return ExecClearTuple(fc_slot);
	}

	/*
	 * 获取下一个 RETURNING 元组。
	 */
	return fc_get_returning_data(fc_node);
}

/*
 * postgresEndDirectModify
 *		完成直接外部表的修改
 */
static void fc_postgresEndDirectModify(ForeignScanState *fc_node)
{
	PgFdwDirectModifyState *fc_dmstate = (PgFdwDirectModifyState *) fc_node->fdw_state;

	/* 如果 dmstate 为 NULL，则我们处于 EXPLAIN 状态；无需操作 */
	if (fc_dmstate == NULL)
		return;

	/* 释放 PGresult */
	if (fc_dmstate->result)
		PQclear(fc_dmstate->result);

	/* 释放远程连接 */
	ReleaseConnection(fc_dmstate->conn);
	fc_dmstate->conn = NULL;

	/* MemoryContext 将自动删除。 */
}

/*
 * postgresExplainForeignScan
 *		为外部表上的 ForeignScan 的 EXPLAIN 生成额外输出
 */
static void fc_postgresExplainForeignScan(ForeignScanState *fc_node, ExplainState *fc_es)
{
	ForeignScan *fc_plan = castNode(ForeignScan, fc_node->ss.ps.plan);
	List	   *fc_fdw_private = fc_plan->fdw_private;

	/*
	 * 识别实际上是连接或上层关系的外部扫描。输入看起来类似于 "(1) LEFT JOIN (2)"，我们必须用正确的关系名称替换数字字符串，这些字符串是 RT 索引。我们在这里执行此操作，而不是在创建计划时，因为我们无法知道 ruleutils.c 在计划创建时会分配什么别名。
	 */
	if (list_length(fc_fdw_private) > FdwScanPrivateRelations)
	{
		StringInfo	fc_relations;
		char	   *fc_rawrelations;
		char	   *fc_ptr;
		int			fc_minrti,
					fc_rtoffset;

		fc_rawrelations = strVal(list_nth(fc_fdw_private, FdwScanPrivateRelations));

		/*
		 * 使用 RT 索引的字符串表示法有一个困难，即 setrefs.c 在扁平化 rangetable 时不会更新该字符串。要找出应用了哪个 rtoffset，识别字符串中出现的最小 RT 索引，并将其与 plan->fs_relids 的最小成员进行比较。（我们期望连接中的所有 relids 都已被相同的数量偏移；下面的断言应该捕捉到如果这种情况有所变化。）
		 */
		fc_minrti = INT_MAX;
		fc_ptr = fc_rawrelations;
		while (*fc_ptr)
		{
			if (isdigit((unsigned char) *fc_ptr))
			{
				int			fc_rti = strtol(fc_ptr, &fc_ptr, 10);

				if (fc_rti < fc_minrti)
					fc_minrti = fc_rti;
			}
			else
				fc_ptr++;
		}
		fc_rtoffset = bms_next_member(fc_plan->fs_relids, -1) - fc_minrti;

		/* 现在我们可以翻译字符串 */
		fc_relations = makeStringInfo();
		fc_ptr = fc_rawrelations;
		while (*fc_ptr)
		{
			if (isdigit((unsigned char) *fc_ptr))
			{
				int			fc_rti = strtol(fc_ptr, &fc_ptr, 10);
				RangeTblEntry *fc_rte;
				char	   *fc_relname;
				char	   *fc_refname;

				fc_rti += fc_rtoffset;
				Assert(bms_is_member(fc_rti, fc_plan->fs_relids));
				fc_rte = rt_fetch(fc_rti, fc_es->rtable);
				Assert(fc_rte->rtekind == RTE_RELATION);
				/* 此逻辑应与 explain.c 的 ExplainTargetRel 一致 */
				fc_relname = get_rel_name(fc_rte->relid);
				if (fc_es->verbose)
				{
					char	   *fc_namespace;

					fc_namespace = get_namespace_name_or_temp(get_rel_namespace(fc_rte->relid));
					appendStringInfo(fc_relations, "%s.%s",
									 quote_identifier(fc_namespace),
									 quote_identifier(fc_relname));
				}
				else
					appendStringInfoString(fc_relations,
										   quote_identifier(fc_relname));
				fc_refname = (char *) list_nth(fc_es->rtable_names, fc_rti - 1);
				if (fc_refname == NULL)
					fc_refname = fc_rte->eref->aliasname;
				if (strcmp(fc_refname, fc_relname) != 0)
					appendStringInfo(fc_relations, " %s",
									 quote_identifier(fc_refname));
			}
			else
				appendStringInfoChar(fc_relations, *fc_ptr++);
		}
		ExplainPropertyText("Relations", fc_relations->data, fc_es);
	}

	/*
	 * 当指定 VERBOSE 选项时，添加远程查询。
	 */
	if (fc_es->verbose)
	{
		char	   *fc_sql;

		fc_sql = strVal(list_nth(fc_fdw_private, FdwScanPrivateSelectSql));
		ExplainPropertyText("Remote SQL", fc_sql, fc_es);
	}
}

/*
 * postgresExplainForeignModify
 *		为外部表上的 ModifyTable 的 EXPLAIN 生成额外输出
 */
static void fc_postgresExplainForeignModify(ModifyTableState *fc_mtstate,
							 ResultRelInfo *fc_rinfo,
							 List *fc_fdw_private,
							 int fc_subplan_index,
							 ExplainState *fc_es)
{
	if (fc_es->verbose)
	{
		char	   *fc_sql = strVal(list_nth(fc_fdw_private,
										  FdwModifyPrivateUpdateSql));

		ExplainPropertyText("Remote SQL", fc_sql, fc_es);

		/*
		 * 对于 INSERT，我们应始终确保批量大小 >= 1，但 UPDATE 和 DELETE 不支持批处理，因此不显示该属性。
		 */
		if (fc_rinfo->ri_BatchSize > 0)
			ExplainPropertyInteger("Batch Size", NULL, fc_rinfo->ri_BatchSize, fc_es);
	}
}

/*
 * postgresExplainDirectModify
 *		为直接修改外部表的 ForeignScan 的 EXPLAIN 生成额外输出
 */
static void fc_postgresExplainDirectModify(ForeignScanState *fc_node, ExplainState *fc_es)
{
	List	   *fc_fdw_private;
	char	   *fc_sql;

	if (fc_es->verbose)
	{
		fc_fdw_private = ((ForeignScan *) fc_node->ss.ps.plan)->fdw_private;
		fc_sql = strVal(list_nth(fc_fdw_private, FdwDirectModifyPrivateUpdateSql));
		ExplainPropertyText("Remote SQL", fc_sql, fc_es);
	}
}

/*
 * postgresExecForeignTruncate
 *		截断一个或多个外部表
 */
static void fc_postgresExecForeignTruncate(List *fc_rels,
							DropBehavior fc_behavior,
							bool fc_restart_seqs)
{
	Oid			fc_serverid = InvalidOid;
	UserMapping *fc_user = NULL;
	PGconn	   *fc_conn = NULL;
	StringInfoData fc_sql;
	ListCell   *fc_lc;
	bool		fc_server_truncatable = true;

	/*
	 * 默认情况下，所有 postgres_fdw 外部表都假定是可截断的。这可以通过每个服务器设置覆盖，反过来又可以通过每个表设置覆盖。
	 */
	foreach(fc_lc, fc_rels)
	{
		ForeignServer *fc_server = NULL;
		Relation	fc_rel = lfirst(fc_lc);
		ForeignTable *fc_table = GetForeignTable(RelationGetRelid(fc_rel));
		ListCell   *fc_cell;
		bool		fc_truncatable;

		/*
		 * 首次通过时，确定远程服务器是否允许截断。由于所有指定的外部表都假定属于同一远程服务器，因此此结果可用于其他外部表。
		 */
		if (!OidIsValid(fc_serverid))
		{
			fc_serverid = fc_table->serverid;
			fc_server = GetForeignServer(fc_serverid);

			foreach(fc_cell, fc_server->options)
			{
				DefElem    *fc_defel = (DefElem *) lfirst(fc_cell);

				if (strcmp(fc_defel->defname, "truncatable") == 0)
				{
					fc_server_truncatable = defGetBoolean(fc_defel);
					break;
				}
			}
		}

		/*
		 * 确认所有指定的外部表属于同一远程服务器。
		 */
		Assert(fc_table->serverid == fc_serverid);

		/* 确定此外部表是否允许截断 */
		fc_truncatable = fc_server_truncatable;
		foreach(fc_cell, fc_table->options)
		{
			DefElem    *fc_defel = (DefElem *) lfirst(fc_cell);

			if (strcmp(fc_defel->defname, "truncatable") == 0)
			{
				fc_truncatable = defGetBoolean(fc_defel);
				break;
			}
		}

		if (!fc_truncatable)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("foreign table \"%s\" does not allow truncates",
							RelationGetRelationName(fc_rel))));
	}
	Assert(OidIsValid(fc_serverid));

	/*
	 * 获取与外部服务器的连接。 连接管理器将
	 * 在必要时建立新的连接。
	 */
	fc_user = GetUserMapping(GetUserId(), fc_serverid);
	fc_conn = GetConnection(fc_user, false, NULL);

	/* 构建 TRUNCATE 命令字符串 */
	initStringInfo(&fc_sql);
	deparseTruncateSql(&fc_sql, fc_rels, fc_behavior, fc_restart_seqs);

	/* 向远程服务器发出 TRUNCATE 命令 */
	do_sql_command(fc_conn, fc_sql.data);

	pfree(fc_sql.data);
}

/*
 * estimate_path_cost_size
 *		获取给定外部关系上的外部扫描的成本和大小估算，
 *		无论是基本关系，还是外部关系之间的连接，或包含外部关系的
 *		上层关系。
 *
 * param_join_conds 是与外部关系相关的参数化子句。
 * pathkeys 指定被计费路径的预期排序顺序（如果有的话）。
 * fpextra 指定额外的后扫描/连接处理步骤，如最终排序和 LIMIT 限制。
 *
 * 此函数在 p_rows、p_width、p_startup_cost 和 p_total_cost 变量中返回成本和大小估算。
 */
static void fc_estimate_path_cost_size(PlannerInfo *fc_root,
						RelOptInfo *fc_foreignrel,
						List *fc_param_join_conds,
						List *fc_pathkeys,
						PgFdwPathExtraData *fc_fpextra,
						double *fc_p_rows, int *fc_p_width,
						Cost *fc_p_startup_cost, Cost *fc_p_total_cost)
{
	PgFdwRelationInfo *fc_fpinfo = (PgFdwRelationInfo *) fc_foreignrel->fdw_private;
	double		fc_rows;
	double		fc_retrieved_rows;
	int			fc_width;
	Cost		fc_startup_cost;
	Cost		fc_total_cost;

	/* 确保核心代码已设置关系的 reltarget */
	Assert(fc_foreignrel->reltarget);

	/*
	 * 如果表或服务器配置为使用远程估算，
	 * 则连接到外部服务器并执行 EXPLAIN 以估算通过限制+连接子句选择的行数。
	 * 否则，使用我们本地的任何统计信息以类似普通表的方式估算行数。
	 */
	if (fc_fpinfo->use_remote_estimate)
	{
		List	   *fc_remote_param_join_conds;
		List	   *fc_local_param_join_conds;
		StringInfoData fc_sql;
		PGconn	   *fc_conn;
		Selectivity fc_local_sel;
		QualCost	fc_local_cost;
		List	   *fc_fdw_scan_tlist = NIL;
		List	   *fc_remote_conds;

		/* 仅在传递给 deparseSelectStmtForRel 时需要 */
		List	   *fc_retrieved_attrs;

		/*
		 * param_join_conds 可能包含可以安全发送的子句
		 * 和无法发送的子句。
		 */
		classifyConditions(fc_root, fc_foreignrel, fc_param_join_conds,
						   &fc_remote_param_join_conds, &fc_local_param_join_conds);

		/* 构建从外部服务器获取的列列表。 */
		if (IS_JOIN_REL(fc_foreignrel) || IS_UPPER_REL(fc_foreignrel))
			fc_fdw_scan_tlist = build_tlist_to_deparse(fc_foreignrel);
		else
			fc_fdw_scan_tlist = NIL;

		/*
		 * 完整的远程条件列表包括来自 baserestrictinfo 的所有内容，以及与此
		 * 特定路径相关的任何额外 join_conds。
		 */
		fc_remote_conds = list_concat(fc_remote_param_join_conds,
								   fc_fpinfo->remote_conds);

		/*
		 * 构建包含所需的 SELECT、FROM 和
		 * WHERE 子句的 EXPLAIN 查询。 参数和其他关系变量将被虚拟值替换，
		 * 因此不请求 params_list。
		 */
		initStringInfo(&fc_sql);
		appendStringInfoString(&fc_sql, "EXPLAIN ");
		deparseSelectStmtForRel(&fc_sql, fc_root, fc_foreignrel, fc_fdw_scan_tlist,
								fc_remote_conds, fc_pathkeys,
								fc_fpextra ? fc_fpextra->has_final_sort : false,
								fc_fpextra ? fc_fpextra->has_limit : false,
								false, &fc_retrieved_attrs, NULL);

		/* 获取远程估算 */
		fc_conn = GetConnection(fc_fpinfo->user, false, NULL);
		fc_get_remote_estimate(fc_sql.data, fc_conn, &fc_rows, &fc_width,
							&fc_startup_cost, &fc_total_cost);
		ReleaseConnection(fc_conn);

		fc_retrieved_rows = fc_rows;

		/* 考虑本地检查的资格的选择性 */
		fc_local_sel = clauselist_selectivity(fc_root,
										   fc_local_param_join_conds,
										   fc_foreignrel->relid,
										   JOIN_INNER,
										   NULL);
		fc_local_sel *= fc_fpinfo->local_conds_sel;

		fc_rows = clamp_row_est(fc_rows * fc_local_sel);

		/* 加入本地检查的资格的评估成本 */
		fc_startup_cost += fc_fpinfo->local_conds_cost.startup;
		fc_total_cost += fc_fpinfo->local_conds_cost.per_tuple * fc_retrieved_rows;
		cost_qual_eval(&fc_local_cost, fc_local_param_join_conds, fc_root);
		fc_startup_cost += fc_local_cost.startup;
		fc_total_cost += fc_local_cost.per_tuple * fc_retrieved_rows;

		/*
		 * 为每个输出行加入 tlist 评估成本。 如果是聚合，某些
		 * tlist 表达式（如分组表达式）将在远程评估，因此调整成本。
		 */
		fc_startup_cost += fc_foreignrel->reltarget->cost.startup;
		fc_total_cost += fc_foreignrel->reltarget->cost.startup;
		fc_total_cost += fc_foreignrel->reltarget->cost.per_tuple * fc_rows;
		if (IS_UPPER_REL(fc_foreignrel))
		{
			QualCost	fc_tlist_cost;

			cost_qual_eval(&fc_tlist_cost, fc_fdw_scan_tlist, fc_root);
			fc_startup_cost -= fc_tlist_cost.startup;
			fc_total_cost -= fc_tlist_cost.startup;
			fc_total_cost -= fc_tlist_cost.per_tuple * fc_rows;
		}
	}
	else
	{
		Cost		fc_run_cost = 0;

		/*
		 * 我们不支持此模式下的连接条件（因此，无法创建参数化路径）。
		 */
		Assert(fc_param_join_conds == NIL);

		/*
		 * 我们将一遍又一遍地到此处，使用不同的 pathkeys 集或
		 * 调用者想要计费的额外后扫描/连接处理步骤。
		 * 我们不需要每次都计算基础扫描、连接或分组的成本/大小估算。
		 * 相反，如果我们已经缓存了这些估算，则使用它们。
		 */
		if (fc_fpinfo->rel_startup_cost >= 0 && fc_fpinfo->rel_total_cost >= 0)
		{
			Assert(fc_fpinfo->retrieved_rows >= 0);

			fc_rows = fc_fpinfo->rows;
			fc_retrieved_rows = fc_fpinfo->retrieved_rows;
			fc_width = fc_fpinfo->width;
			fc_startup_cost = fc_fpinfo->rel_startup_cost;
			fc_run_cost = fc_fpinfo->rel_total_cost - fc_fpinfo->rel_startup_cost;

			/*
			 * 如果我们估算外部扫描或外部连接的成本，并且有额外的后处理步骤，
			 * 那么从缓存中获得的扫描或连接成本还不包含最终扫描/连接目标的评估成本，
			 * 这些成本已通过apply_scanjoin_target_to_paths()进行更新；现在添加评估成本。
			 */
			if (fc_fpextra && !IS_UPPER_REL(fc_foreignrel))
			{
				/* 不应在这里，除非我们有LIMIT */
				Assert(fc_fpextra->has_limit);
				Assert(fc_foreignrel->reloptkind == RELOPT_BASEREL ||
					   fc_foreignrel->reloptkind == RELOPT_JOINREL);
				fc_startup_cost += fc_foreignrel->reltarget->cost.startup;
				fc_run_cost += fc_foreignrel->reltarget->cost.per_tuple * fc_rows;
			}
		}
		else if (IS_JOIN_REL(fc_foreignrel))
		{
			PgFdwRelationInfo *fc_fpinfo_i;
			PgFdwRelationInfo *fc_fpinfo_o;
			QualCost	fc_join_cost;
			QualCost	fc_remote_conds_cost;
			double		fc_nrows;

			/* 使用核心代码做出的行/宽度估算。 */
			fc_rows = fc_foreignrel->rows;
			fc_width = fc_foreignrel->reltarget->width;

			/* 对于连接，我们期望有内外关系集 */
			Assert(fc_fpinfo->innerrel && fc_fpinfo->outerrel);

			fc_fpinfo_i = (PgFdwRelationInfo *) fc_fpinfo->innerrel->fdw_private;
			fc_fpinfo_o = (PgFdwRelationInfo *) fc_fpinfo->outerrel->fdw_private;

			/* 交叉乘积中行的估算数 */
			fc_nrows = fc_fpinfo_i->rows * fc_fpinfo_o->rows;

			/*
			 * 回到一个检索行数的估算。以防万一这很疯狂，限制为最多nrows。
			 */
			fc_retrieved_rows = clamp_row_est(fc_rows / fc_fpinfo->local_conds_sel);
			fc_retrieved_rows = Min(fc_retrieved_rows, fc_nrows);

			/*
			 * 外部连接的成本估算为生成连接关系的行的成本 + 在行上应用条件的成本。
			 */

			/*
			 * 计算推送到外部服务器的子句的成本
			 */
			cost_qual_eval(&fc_remote_conds_cost, fc_fpinfo->remote_conds, fc_root);
			/* 计算应用连接子句的成本 */
			cost_qual_eval(&fc_join_cost, fc_fpinfo->joinclauses, fc_root);

			/*
			 * 启动成本包括连接关系的启动成本和连接及其他子句的启动成本。
			 * 我们不包括与连接策略特定的启动成本（例如，设置哈希表），
			 * 因为我们不知道外部服务器将使用什么策略。
			 */
			fc_startup_cost = fc_fpinfo_i->rel_startup_cost + fc_fpinfo_o->rel_startup_cost;
			fc_startup_cost += fc_join_cost.startup;
			fc_startup_cost += fc_remote_conds_cost.startup;
			fc_startup_cost += fc_fpinfo->local_conds_cost.startup;

			/*
			 * 运行时间成本包括：
			 *
			 * 1. 关系的运行时间成本（总成本 - 启动成本）
			 *
			 * 2. 在连接关系的交叉乘积上应用连接子句的运行时间成本。
			 *
			 * 3. 在连接结果上应用推送下来的其他子句的运行时间成本。
			 *
			 * 4. 在从外部服务器获取的结果上本地应用不可推送的其他子句的运行时间成本。
			 */
			fc_run_cost = fc_fpinfo_i->rel_total_cost - fc_fpinfo_i->rel_startup_cost;
			fc_run_cost += fc_fpinfo_o->rel_total_cost - fc_fpinfo_o->rel_startup_cost;
			fc_run_cost += fc_nrows * fc_join_cost.per_tuple;
			fc_nrows = clamp_row_est(fc_nrows * fc_fpinfo->joinclause_sel);
			fc_run_cost += fc_nrows * fc_remote_conds_cost.per_tuple;
			fc_run_cost += fc_fpinfo->local_conds_cost.per_tuple * fc_retrieved_rows;

			/* 为每个输出行添加tlist评估成本 */
			fc_startup_cost += fc_foreignrel->reltarget->cost.startup;
			fc_run_cost += fc_foreignrel->reltarget->cost.per_tuple * fc_rows;
		}
		else if (IS_UPPER_REL(fc_foreignrel))
		{
			RelOptInfo *fc_outerrel = fc_fpinfo->outerrel;
			PgFdwRelationInfo *fc_ofpinfo;
			AggClauseCosts fc_aggcosts;
			double		fc_input_rows;
			int			fc_numGroupCols;
			double		fc_numGroups = 1;

			/* 上级关系应具有其外部关系集 */
			Assert(fc_outerrel);
			/* 并且该外部关系应具有其目标设置 */
			Assert(fc_outerrel->reltarget);

			/*
			 * 此成本模型是cost_agg()中对排序和哈希聚合的成本混合估算。
			 * 我们不确定远程端将考虑哪种策略，因此为简单起见，
			 * 我们将所有与启动相关的成本放入启动成本中，
			 * 所有的最终成本和运行成本都加到总成本中。
			 */

			fc_ofpinfo = (PgFdwRelationInfo *) fc_outerrel->fdw_private;

			/* 从输入关系获取行 */
			fc_input_rows = fc_ofpinfo->rows;

			/* 收集有关聚合的统计信息以估算成本。 */
			MemSet(&fc_aggcosts, 0, sizeof(AggClauseCosts));
			if (fc_root->parse->hasAggs)
			{
				get_agg_clause_costs(fc_root, AGGSPLIT_SIMPLE, &fc_aggcosts);
			}

			/* 获取分组列数和可能的组数 */
			fc_numGroupCols = list_length(fc_root->parse->groupClause);
			fc_numGroups = estimate_num_groups(fc_root,
											get_sortgrouplist_exprs(fc_root->parse->groupClause,
																	fc_fpinfo->grouped_tlist),
											fc_input_rows, NULL, NULL);

			/*
			 * 获取检索行和行的估算。如果有HAVING条件，应考虑其选择性。
			 */
			if (fc_root->parse->havingQual)
			{
				/* 考虑远程检查条件的选择性 */
				fc_retrieved_rows =
					clamp_row_est(fc_numGroups *
								  clauselist_selectivity(fc_root,
														 fc_fpinfo->remote_conds,
														 0,
														 JOIN_INNER,
														 NULL));
				/* 考虑本地检查的资格的选择性 */
				fc_rows = clamp_row_est(fc_retrieved_rows * fc_fpinfo->local_conds_sel);
			}
			else
			{
				fc_rows = fc_retrieved_rows = fc_numGroups;
			}

			/* 使用核心代码做出的宽度估算。 */
			fc_width = fc_foreignrel->reltarget->width;

			/*-----
			 * 启动成本包括：
			 *	  1. 对底层输入关系的启动成本，根据apply_scanjoin_target_to_paths()调整tlist替换
			 *	  2. 执行聚合的成本，按照cost_agg()计算
			 *-----
			 */
			fc_startup_cost = fc_ofpinfo->rel_startup_cost;
			fc_startup_cost += fc_outerrel->reltarget->cost.startup;
			fc_startup_cost += fc_aggcosts.transCost.startup;
			fc_startup_cost += fc_aggcosts.transCost.per_tuple * fc_input_rows;
			fc_startup_cost += fc_aggcosts.finalCost.startup;
			fc_startup_cost += (cpu_operator_cost * fc_numGroupCols) * fc_input_rows;

			/*-----
			 * 运行时间成本包括：
			 *	  1. 对底层输入关系的运行时间成本，根据apply_scanjoin_target_to_paths()调整tlist替换
			 *	  2. 执行聚合的运行时间成本，按照cost_agg()计算
			 *-----
			 */
			fc_run_cost = fc_ofpinfo->rel_total_cost - fc_ofpinfo->rel_startup_cost;
			fc_run_cost += fc_outerrel->reltarget->cost.per_tuple * fc_input_rows;
			fc_run_cost += fc_aggcosts.finalCost.per_tuple * fc_numGroups;
			fc_run_cost += cpu_tuple_cost * fc_numGroups;

			/* 计算HAVING条件的评估成本（如果有的话） */
			if (fc_root->parse->havingQual)
			{
				QualCost	fc_remote_cost;

				/* 加入远程检查条件的评估成本 */
				cost_qual_eval(&fc_remote_cost, fc_fpinfo->remote_conds, fc_root);
				fc_startup_cost += fc_remote_cost.startup;
				fc_run_cost += fc_remote_cost.per_tuple * fc_numGroups;
				/* 加入本地检查的资格的评估成本 */
				fc_startup_cost += fc_fpinfo->local_conds_cost.startup;
				fc_run_cost += fc_fpinfo->local_conds_cost.per_tuple * fc_retrieved_rows;
			}

			/* 为每个输出行添加tlist评估成本 */
			fc_startup_cost += fc_foreignrel->reltarget->cost.startup;
			fc_run_cost += fc_foreignrel->reltarget->cost.per_tuple * fc_rows;
		}
		else
		{
			Cost		fc_cpu_per_tuple;

			/* 使用set_baserel_size_estimates做出的行数/宽度估算。 */
			fc_rows = fc_foreignrel->rows;
			fc_width = fc_foreignrel->reltarget->width;

			/*
			 * 反向推算检索到的行数估算。以防万一，限制为外部关系的元组数。
			 */
			fc_retrieved_rows = clamp_row_est(fc_rows / fc_fpinfo->local_conds_sel);
			fc_retrieved_rows = Min(fc_retrieved_rows, fc_foreignrel->tuples);

			/*
			 * 成本视为这是一个顺序扫描，这是一种悲观估计。我们有效地想象本地条件也在远程评估。
			 */
			fc_startup_cost = 0;
			fc_run_cost = 0;
			fc_run_cost += seq_page_cost * fc_foreignrel->pages;

			fc_startup_cost += fc_foreignrel->baserestrictcost.startup;
			fc_cpu_per_tuple = cpu_tuple_cost + fc_foreignrel->baserestrictcost.per_tuple;
			fc_run_cost += fc_cpu_per_tuple * fc_foreignrel->tuples;

			/* 为每个输出行添加tlist评估成本 */
			fc_startup_cost += fc_foreignrel->reltarget->cost.startup;
			fc_run_cost += fc_foreignrel->reltarget->cost.per_tuple * fc_rows;
		}

		/*
		 * 如果没有远程估算，我们就没有真正的方法来估算生成排序输出的成本。
		 * 如果查询计划所选择的远程端生成正确排序的输出，这可能是免费的，
		 * 但在大多数情况下，它会花费一些成本。估算一个足够高的值，以便在排序不是本地有用时
		 * 我们不会选择排序路径，但又要足够低，以便在排序有用时倾向于下推ORDER BY子句。
		 */
		if (fc_pathkeys != NIL)
		{
			if (IS_UPPER_REL(fc_foreignrel))
			{
				Assert(fc_foreignrel->reloptkind == RELOPT_UPPER_REL &&
					   fc_fpinfo->stage == UPPERREL_GROUP_AGG);
				fc_adjust_foreign_grouping_path_cost(fc_root, fc_pathkeys,
												  fc_retrieved_rows, fc_width,
												  fc_fpextra->limit_tuples,
												  &fc_startup_cost, &fc_run_cost);
			}
			else
			{
				fc_startup_cost *= DEFAULT_FDW_SORT_MULTIPLIER;
				fc_run_cost *= DEFAULT_FDW_SORT_MULTIPLIER;
			}
		}

		fc_total_cost = fc_startup_cost + fc_run_cost;

		/* 如果有LIMIT，则调整成本估算 */
		if (fc_fpextra && fc_fpextra->has_limit)
		{
			adjust_limit_rows_costs(&fc_rows, &fc_startup_cost, &fc_total_cost,
									fc_fpextra->offset_est, fc_fpextra->count_est);
			fc_retrieved_rows = fc_rows;
		}
	}

	/*
	 * 如果这包括最终的排序步骤，给定的目标将应用于生成的路径，可能与外部关系的目标表达式
	 * 不同（见make_sort_input_target()）；调整tlist评估成本。
	 */
	if (fc_fpextra && fc_fpextra->has_final_sort &&
		fc_fpextra->target != fc_foreignrel->reltarget)
	{
		QualCost	fc_oldcost = fc_foreignrel->reltarget->cost;
		QualCost	fc_newcost = fc_fpextra->target->cost;

		fc_startup_cost += fc_newcost.startup - fc_oldcost.startup;
		fc_total_cost += fc_newcost.startup - fc_oldcost.startup;
		fc_total_cost += (fc_newcost.per_tuple - fc_oldcost.per_tuple) * fc_rows;
	}

	/*
	 * 缓存检索到的行和没有任何参数化、路径键或额外后扫描/连接处理步骤的扫描、连接或分组成本估算，
	 * 然后添加从外部服务器传输数据的成本。这些估算对于涉及此关系的远程连接成本或
	 * 在无法从外部服务器获取成本时，对此关系的其他远程操作成本（如远程排序和远程LIMIT限制）
	 * 是有用的。此函数将至少为每个没有任何参数化、路径键或额外后扫描/连接处理步骤的外部关系调用一次。
	 */
	if (fc_pathkeys == NIL && fc_param_join_conds == NIL && fc_fpextra == NULL)
	{
		fc_fpinfo->retrieved_rows = fc_retrieved_rows;
		fc_fpinfo->rel_startup_cost = fc_startup_cost;
		fc_fpinfo->rel_total_cost = fc_total_cost;
	}

	/*
	 * 添加一些额外的成本因素来考虑连接开销（fdw_startup_cost），
	 * 通过网络传输数据的成本（每检索到一行的fdw_tuple_cost），
	 * 以及数据的本地操作（每检索到一行的cpu_tuple_cost）。
	 */
	fc_startup_cost += fc_fpinfo->fdw_startup_cost;
	fc_total_cost += fc_fpinfo->fdw_startup_cost;
	fc_total_cost += fc_fpinfo->fdw_tuple_cost * fc_retrieved_rows;
	fc_total_cost += cpu_tuple_cost * fc_retrieved_rows;

	/*
	 * 如果我们有 LIMIT，我们应该优先选择远程执行限制
	 * 而不是本地，因为前者可以避免由于后者可能导致的额外行提取。
	 * 但是由于核心代码在估算本地限制的成本时没有考虑这种提取（见 create_limit_path()），
	 * 如果我们不使用远程估算，那么本地限制的成本与上述估算的远程限制的成本之间将没有差别
	 * （除了当 foreignrel 是一个分组关系，给定的 pathkeys 不是 NIL，
	 * 并且该关系的有界排序的效果在估算远程限制时被考虑的情况）。
	 * 调整远程限制的成本以确保如果 LIMIT 是有用的，我们会优先选择它。
	 */
	if (!fc_fpinfo->use_remote_estimate &&
		fc_fpextra && fc_fpextra->has_limit &&
		fc_fpextra->limit_tuples > 0 &&
		fc_fpextra->limit_tuples < fc_fpinfo->rows)
	{
		Assert(fc_fpinfo->rows > 0);
		fc_total_cost -= (fc_total_cost - fc_startup_cost) * 0.05 *
			(fc_fpinfo->rows - fc_fpextra->limit_tuples) / fc_fpinfo->rows;
	}

	/* 返回结果。 */
	*fc_p_rows = fc_rows;
	*fc_p_width = fc_width;
	*fc_p_startup_cost = fc_startup_cost;
	*fc_p_total_cost = fc_total_cost;
}

/*
 * 估算远程执行 SQL 语句的成本。
 * 给定的“sql”必须是一个 EXPLAIN 命令。
 */
static void fc_get_remote_estimate(const char *fc_sql, PGconn *fc_conn,
					double *fc_rows, int *fc_width,
					Cost *fc_startup_cost, Cost *fc_total_cost)
{
	PGresult   *volatile fc_res = NULL;

	/* 在离开此函数之前必须释放 PGresult。 */
	PG_TRY();
	{
		char	   *fc_line;
		char	   *fc_p;
		int			fc_n;

		/*
		 * 远程执行 EXPLAIN。
		 */
		fc_res = pgfdw_exec_query(fc_conn, fc_sql, NULL);
		if (PQresultStatus(fc_res) != PGRES_TUPLES_OK)
			pgfdw_report_error(ERROR, fc_res, fc_conn, false, fc_sql);

		/*
		 * 提取最上层计划节点的成本数字。 注意我们从行末搜索左括号，
		 * 以避免被其他括号用法所混淆。
		 */
		fc_line = PQgetvalue(fc_res, 0, 0);
		fc_p = strrchr(fc_line, '(');
		if (fc_p == NULL)
			elog(ERROR, "could not interpret EXPLAIN output: \"%s\"", fc_line);
		fc_n = sscanf(fc_p, "(cost=%lf..%lf rows=%lf width=%d)",
				   fc_startup_cost, fc_total_cost, fc_rows, fc_width);
		if (fc_n != 4)
			elog(ERROR, "could not interpret EXPLAIN output: \"%s\"", fc_line);
	}
	PG_FINALLY();
	{
		if (fc_res)
			PQclear(fc_res);
	}
	PG_END_TRY();
}

/*
 * 调整外部分组路径的成本估算，以包括生成正确排序输出的成本。
 */
static void fc_adjust_foreign_grouping_path_cost(PlannerInfo *fc_root,
								  List *fc_pathkeys,
								  double fc_retrieved_rows,
								  double fc_width,
								  double fc_limit_tuples,
								  Cost *fc_p_startup_cost,
								  Cost *fc_p_run_cost)
{
	/*
	 * 如果 GROUP BY 子句不可排序，远端选择的计划不太可能生成
	 * 正确排序的输出，因此需要进行显式排序；使用 cost_sort()
	 * 调整给定的成本。同样，如果 GROUP BY 子句可排序但不是给定路径键的超集，
	 * 则使用该函数调整成本。否则，通过应用与扫描或连接情况相同的启发式方法来调整成本。
	 */
	if (!grouping_is_sortable(fc_root->parse->groupClause) ||
		!pathkeys_contained_in(fc_pathkeys, fc_root->group_pathkeys))
	{
		Path		fc_sort_path;	/* cost_sort 结果的占位符 */

		cost_sort(&fc_sort_path,
				  fc_root,
				  fc_pathkeys,
				  *fc_p_startup_cost + *fc_p_run_cost,
				  fc_retrieved_rows,
				  fc_width,
				  0.0,
				  work_mem,
				  fc_limit_tuples);

		*fc_p_startup_cost = fc_sort_path.startup_cost;
		*fc_p_run_cost = fc_sort_path.total_cost - fc_sort_path.startup_cost;
	}
	else
	{
		/*
		 * 对于外部分组情况，默认的额外成本似乎过大；
		 * 添加该默认值的四分之一。
		 */
		double		fc_sort_multiplier = 1.0 + (DEFAULT_FDW_SORT_MULTIPLIER
											 - 1.0) * 0.25;

		*fc_p_startup_cost *= fc_sort_multiplier;
		*fc_p_run_cost *= fc_sort_multiplier;
	}
}

/*
 * 检测我们是否要处理 EquivalenceClass 成员。
 *
 * 这是 generate_implied_equalities_for_column 使用的回调。
 */
static bool fc_ec_member_matches_foreign(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						  EquivalenceClass *fc_ec, EquivalenceMember *fc_em,
						  void *fc_arg)
{
	ec_member_foreign_arg *fc_state = (ec_member_foreign_arg *) fc_arg;
	Expr	   *fc_expr = fc_em->em_expr;

	/*
	 * 如果我们已经确定在当前扫描中正在处理的内容，我们只想匹配该表达式。
	 */
	if (fc_state->current != NULL)
		return equal(fc_expr, fc_state->current);

	/*
	 * 否则，忽略我们已经处理的任何内容。
	 */
	if (list_member(fc_state->already_used, fc_expr))
		return false;

	/* 这是要处理的新目标。 */
	fc_state->current = fc_expr;
	return true;
}

/*
 * 为节点的查询创建游标，使用当前参数值。
 */
static void fc_create_cursor(ForeignScanState *fc_node)
{
	PgFdwScanState *fc_fsstate = (PgFdwScanState *) fc_node->fdw_state;
	ExprContext *fc_econtext = fc_node->ss.ps.ps_ExprContext;
	int			fc_numParams = fc_fsstate->numParams;
	const char **fc_values = fc_fsstate->param_values;
	PGconn	   *fc_conn = fc_fsstate->conn;
	StringInfoData fc_buf;
	PGresult   *fc_res;

	/* 首先，处理任何待处理的异步请求。 */
	if (fc_fsstate->conn_state->pendingAreq)
		process_pending_request(fc_fsstate->conn_state->pendingAreq);

	/*
	 * 以文本格式构造查询参数值数组。我们在短暂的逐元组上下文中进行转换，
	 * 以避免在重复扫描时造成内存泄漏。
	 */
	if (fc_numParams > 0)
	{
		MemoryContext fc_oldcontext;

		fc_oldcontext = MemoryContextSwitchTo(fc_econtext->ecxt_per_tuple_memory);

		fc_process_query_params(fc_econtext,
							 fc_fsstate->param_flinfo,
							 fc_fsstate->param_exprs,
							 fc_values);

		MemoryContextSwitchTo(fc_oldcontext);
	}

	/* 构造 DECLARE CURSOR 命令 */
	initStringInfo(&fc_buf);
	appendStringInfo(&fc_buf, "DECLARE c%u CURSOR FOR\n%s",
					 fc_fsstate->cursor_number, fc_fsstate->query);

	/*
	 * 注意，我们为 paramTypes 传递 NULL，从而强制远程服务器
	 * 推断所有参数的类型。由于我们显式地转换每个
	 * 参数（见 deparse.c），因此“推断”是微不足道的，
	 * 将产生期望的结果。这使我们能够避免假设远程
	 * 服务器有与我们相同的参数类型 OID。
	 */
	if (!PQsendQueryParams(fc_conn, fc_buf.data, fc_numParams,
						   NULL, fc_values, NULL, NULL, 0))
		pgfdw_report_error(ERROR, NULL, fc_conn, false, fc_buf.data);

	/*
	 * 获取结果，并检查成功。
	 *
	 * 我们在这里不使用 PG_TRY 块，因此请小心不要在未释放
	 * PGresult 的情况下抛出错误。
	 */
	fc_res = pgfdw_get_result(fc_conn, fc_buf.data);
	if (PQresultStatus(fc_res) != PGRES_COMMAND_OK)
		pgfdw_report_error(ERROR, fc_res, fc_conn, true, fc_fsstate->query);
	PQclear(fc_res);

	/* 将游标标记为已创建，并显示未检索到元组 */
	fc_fsstate->cursor_exists = true;
	fc_fsstate->tuples = NULL;
	fc_fsstate->num_tuples = 0;
	fc_fsstate->next_tuple = 0;
	fc_fsstate->fetch_ct_2 = 0;
	fc_fsstate->eof_reached = false;

	/* 清理 */
	pfree(fc_buf.data);
}

/*
 * 从节点的游标中获取更多行。
 */
static void fc_fetch_more_data(ForeignScanState *fc_node)
{
	PgFdwScanState *fc_fsstate = (PgFdwScanState *) fc_node->fdw_state;
	PGresult   *volatile fc_res = NULL;
	MemoryContext fc_oldcontext;

	/*
	 * 我们将在 batch_cxt 中存储元组。首先，刷新先前的
	 * 批次。
	 */
	fc_fsstate->tuples = NULL;
	MemoryContextReset(fc_fsstate->batch_cxt);
	fc_oldcontext = MemoryContextSwitchTo(fc_fsstate->batch_cxt);

	/* 在离开此函数之前必须释放 PGresult。 */
	PG_TRY();
	{
		PGconn	   *fc_conn = fc_fsstate->conn;
		int			fc_numrows;
		int			fc_i;

		if (fc_fsstate->async_capable)
		{
			Assert(fc_fsstate->conn_state->pendingAreq);

			/*
			 * 查询已通过先前对
			 * fetch_more_data_begin 的调用发送。现在我们只需获取结果。
			 */
			fc_res = pgfdw_get_result(fc_conn, fc_fsstate->query);
			/* 出错时，报告原始查询，而不是 FETCH。 */
			if (PQresultStatus(fc_res) != PGRES_TUPLES_OK)
				pgfdw_report_error(ERROR, fc_res, fc_conn, false, fc_fsstate->query);

			/* 重置每个连接状态 */
			fc_fsstate->conn_state->pendingAreq = NULL;
		}
		else
		{
			char		fc_sql[64];

			/* 这是一个常规的同步获取。 */
			snprintf(fc_sql, sizeof(fc_sql), "FETCH %d FROM c%u",
					 fc_fsstate->fetch_size, fc_fsstate->cursor_number);

			fc_res = pgfdw_exec_query(fc_conn, fc_sql, fc_fsstate->conn_state);
			/* 出错时，报告原始查询，而不是 FETCH。 */
			if (PQresultStatus(fc_res) != PGRES_TUPLES_OK)
				pgfdw_report_error(ERROR, fc_res, fc_conn, false, fc_fsstate->query);
		}

		/* 将数据转换为 HeapTuples */
		fc_numrows = PQntuples(fc_res);
		fc_fsstate->tuples = (HeapTuple *) palloc0(fc_numrows * sizeof(HeapTuple));
		fc_fsstate->num_tuples = fc_numrows;
		fc_fsstate->next_tuple = 0;

		for (fc_i = 0; fc_i < fc_numrows; fc_i++)
		{
			Assert(IsA(fc_node->ss.ps.plan, ForeignScan));

			fc_fsstate->tuples[fc_i] =
				fc_make_tuple_from_result_row(fc_res, fc_i,
										   fc_fsstate->rel,
										   fc_fsstate->attinmeta,
										   fc_fsstate->retrieved_attrs,
										   fc_node,
										   fc_fsstate->temp_cxt);
		}

		/* 更新 fetch_ct_2 */
		if (fc_fsstate->fetch_ct_2 < 2)
			fc_fsstate->fetch_ct_2++;

		/* 如果我们没有获取到请求的元组数量，则必须是 EOF。 */
		fc_fsstate->eof_reached = (fc_numrows < fc_fsstate->fetch_size);
	}
	PG_FINALLY();
	{
		if (fc_res)
			PQclear(fc_res);
	}
	PG_END_TRY();

	MemoryContextSwitchTo(fc_oldcontext);
}

/*
 * 强制各种 GUC 参数设置为确保我们将以一种不模糊的形式
 * 输出数据值，供远程服务器使用。
 *
 * 对每一行来说，这样做相当昂贵且烦人，但如果我们想确保
 * 值被准确传输，这几乎是别无选择；我们不能在行之间
 * 保留设置，以免影响用户可见的计算。
 *
 * 我们使用等效的函数 SET 选项来允许设置仅在调用
 * reset_transmission_modes() 之前保持有效。如果中间抛出
 * 错误，guc.c 会负责撤销设置。
 *
 * 返回值是必须传递给 reset_transmission_modes() 的
 * nestlevel 以撤销这些操作。
 */
int set_transmission_modes(void)
{
	int			fc_nestlevel = NewGUCNestLevel();

	/*
	 * 此处设置的值应与 pg_dump 的行为相匹配。另见
	 * connection.c 中的 configure_remote_session。
	 */
	if (DateStyle != USE_ISO_DATES)
		(void) set_config_option("datestyle", "ISO",
								 PGC_USERSET, PGC_S_SESSION,
								 GUC_ACTION_SAVE, true, 0, false);
	if (IntervalStyle != INTSTYLE_POSTGRES)
		(void) set_config_option("intervalstyle", "postgres",
								 PGC_USERSET, PGC_S_SESSION,
								 GUC_ACTION_SAVE, true, 0, false);
	if (extra_float_digits < 3)
		(void) set_config_option("extra_float_digits", "3",
								 PGC_USERSET, PGC_S_SESSION,
								 GUC_ACTION_SAVE, true, 0, false);

	/*
	 * 此外，强制限制搜索路径，以防有任何
	 * regproc 或类似常量需要打印。
	 */
	(void) set_config_option("search_path", "pg_catalog",
							 PGC_USERSET, PGC_S_SESSION,
							 GUC_ACTION_SAVE, true, 0, false);

	return fc_nestlevel;
}

/*
 * 撤销 set_transmission_modes() 的影响。
 */
void reset_transmission_modes(int fc_nestlevel)
{
	AtEOXact_GUC(true, fc_nestlevel);
}

/*
 * 关闭游标的实用例程。
 */
static void fc_close_cursor(PGconn *fc_conn, unsigned int fc_cursor_number,
			 PgFdwConnState *fc_conn_state)
{
	char		fc_sql[64];
	PGresult   *fc_res;

	snprintf(fc_sql, sizeof(fc_sql), "CLOSE c%u", fc_cursor_number);

	/*
	 * 我们在这里不使用 PG_TRY 块，所以要小心不要在不释放 PGresult 的
	 * 情况下抛出错误。
	 */
	fc_res = pgfdw_exec_query(fc_conn, fc_sql, fc_conn_state);
	if (PQresultStatus(fc_res) != PGRES_COMMAND_OK)
		pgfdw_report_error(ERROR, fc_res, fc_conn, true, fc_sql);
	PQclear(fc_res);
}

/*
 * create_foreign_modify
 *		构造外部插入/更新/删除操作的执行状态
 */
static PgFdwModifyState *
fc_create_foreign_modify(EState *fc_estate,
					  RangeTblEntry *fc_rte,
					  ResultRelInfo *fc_resultRelInfo,
					  CmdType fc_operation,
					  Plan *fc_subplan,
					  char *fc_query,
					  List *fc_target_attrs,
					  int fc_values_end,
					  bool fc_has_returning,
					  List *fc_retrieved_attrs)
{
	PgFdwModifyState *fc_fmstate;
	Relation	fc_rel = fc_resultRelInfo->ri_RelationDesc;
	TupleDesc	fc_tupdesc = RelationGetDescr(fc_rel);
	Oid			fc_userid;
	ForeignTable *fc_table;
	UserMapping *fc_user;
	AttrNumber	fc_n_params;
	Oid			fc_typefnoid;
	bool		fc_isvarlena;
	ListCell   *fc_lc;

	/* 开始构造 PgFdwModifyState。 */
	fc_fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
	fc_fmstate->rel = fc_rel;

	/*
	 * 确定作为哪个用户进行远程访问。这应与
	 * ExecCheckRTEPerms() 的操作相匹配。
	 */
	fc_userid = fc_rte->checkAsUser ? fc_rte->checkAsUser : GetUserId();

	/* 获取关于外部表的信息。 */
	fc_table = GetForeignTable(RelationGetRelid(fc_rel));
	fc_user = GetUserMapping(fc_userid, fc_table->serverid);

	/* 打开连接；报告我们将创建一个准备好的语句。 */
	fc_fmstate->conn = GetConnection(fc_user, true, &fc_fmstate->conn_state);
	fc_fmstate->p_name = NULL;		/* 准备好的语句尚未创建 */

	/* 设置远程查询信息。 */
	fc_fmstate->query = fc_query;
	if (fc_operation == CMD_INSERT)
	{
		fc_fmstate->query = pstrdup(fc_fmstate->query);
		fc_fmstate->orig_query = pstrdup(fc_fmstate->query);
	}
	fc_fmstate->target_attrs = fc_target_attrs;
	fc_fmstate->values_end = fc_values_end;
	fc_fmstate->has_returning = fc_has_returning;
	fc_fmstate->retrieved_attrs = fc_retrieved_attrs;

	/* 为每个元组创建临时工作区的上下文。 */
	fc_fmstate->temp_cxt = AllocSetContextCreate(fc_estate->es_query_cxt,
											  "postgres_fdw temporary data",
											  ALLOCSET_SMALL_SIZES);

	/* 为 RETURNING 结果的输入转换做准备。 */
	if (fc_fmstate->has_returning)
		fc_fmstate->attinmeta = TupleDescGetAttInMetadata(fc_tupdesc);

	/* 准备在准备好的语句中使用的参数的输出转换。 */
	fc_n_params = list_length(fc_fmstate->target_attrs) + 1;
	fc_fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * fc_n_params);
	fc_fmstate->p_nums = 0;

	if (fc_operation == CMD_UPDATE || fc_operation == CMD_DELETE)
	{
		Assert(fc_subplan != NULL);

		/* 在子计划结果中查找ctid resjunk列 */
		fc_fmstate->ctidAttno = ExecFindJunkAttributeInTlist(fc_subplan->targetlist,
														  "ctid");
		if (!AttributeNumberIsValid(fc_fmstate->ctidAttno))
			elog(ERROR, "could not find junk ctid column");

		/* 第一个可传递的参数将是ctid */
		getTypeOutputInfo(TIDOID, &fc_typefnoid, &fc_isvarlena);
		fmgr_info(fc_typefnoid, &fc_fmstate->p_flinfo[fc_fmstate->p_nums]);
		fc_fmstate->p_nums++;
	}

	if (fc_operation == CMD_INSERT || fc_operation == CMD_UPDATE)
	{
		/* 设置剩余的可传递参数 */
		foreach(fc_lc, fc_fmstate->target_attrs)
		{
			int			fc_attnum = lfirst_int(fc_lc);
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_attnum - 1);

			Assert(!fc_attr->attisdropped);

			/* 忽略生成的列；它们被设置为DEFAULT */
			if (fc_attr->attgenerated)
				continue;
			getTypeOutputInfo(fc_attr->atttypid, &fc_typefnoid, &fc_isvarlena);
			fmgr_info(fc_typefnoid, &fc_fmstate->p_flinfo[fc_fmstate->p_nums]);
			fc_fmstate->p_nums++;
		}
	}

	Assert(fc_fmstate->p_nums <= fc_n_params);

	/* 从远程服务器/表选项中设置batch_size。 */
	if (fc_operation == CMD_INSERT)
		fc_fmstate->batch_size = fc_get_batch_size_option(fc_rel);

	fc_fmstate->num_slots = 1;

	/* 初始化辅助状态 */
	fc_fmstate->aux_fmstate = NULL;

	return fc_fmstate;
}

/*
 * execute_foreign_modify
 *		根据需要执行外部表修改，并获取返回结果
 *		（这是postgresExecForeignInsert、postgresExecForeignBatchInsert、postgresExecForeignUpdate和
 *		postgresExecForeignDelete的共享核心。）
 */
static TupleTableSlot **
fc_execute_foreign_modify(EState *fc_estate,
					   ResultRelInfo *fc_resultRelInfo,
					   CmdType fc_operation,
					   TupleTableSlot **fc_slots,
					   TupleTableSlot **fc_planSlots,
					   int *fc_numSlots)
{
	PgFdwModifyState *fc_fmstate = (PgFdwModifyState *) fc_resultRelInfo->ri_FdwState;
	ItemPointer fc_ctid = NULL;
	const char **fc_p_values;
	PGresult   *fc_res;
	int			fc_n_rows;
	StringInfoData fc_sql;

	/* 操作应为INSERT、UPDATE或DELETE */
	Assert(fc_operation == CMD_INSERT ||
		   fc_operation == CMD_UPDATE ||
		   fc_operation == CMD_DELETE);

	/* 首先，处理任何待处理的异步请求。 */
	if (fc_fmstate->conn_state->pendingAreq)
		process_pending_request(fc_fmstate->conn_state->pendingAreq);

	/*
	 * 如果现有查询已被解析并准备好处理不同数量的行，
	 * 请重新构建以适应正确的数量。
	 */
	if (fc_operation == CMD_INSERT && fc_fmstate->num_slots != *fc_numSlots)
	{
		/* 销毁之前创建的准备语句 */
		if (fc_fmstate->p_name)
			fc_deallocate_query(fc_fmstate);

		/* 使用numSlots记录构建INSERT字符串，其VALUES子句中包含。 */
		initStringInfo(&fc_sql);
		rebuildInsertSql(&fc_sql, fc_fmstate->rel,
						 fc_fmstate->orig_query, fc_fmstate->target_attrs,
						 fc_fmstate->values_end, fc_fmstate->p_nums,
						 *fc_numSlots - 1);
		pfree(fc_fmstate->query);
		fc_fmstate->query = fc_sql.data;
		fc_fmstate->num_slots = *fc_numSlots;
	}

	/* 如果尚未在远程服务器上设置准备语句，则设置之 */
	if (!fc_fmstate->p_name)
		fc_prepare_foreign_modify(fc_fmstate);

	/*
	 * 对于UPDATE/DELETE，获取作为resjunk列传递的ctid
	 */
	if (fc_operation == CMD_UPDATE || fc_operation == CMD_DELETE)
	{
		Datum		fc_datum;
		bool		fc_isNull;

		fc_datum = ExecGetJunkAttribute(fc_planSlots[0],
									 fc_fmstate->ctidAttno,
									 &fc_isNull);
		/* 不应该得到空结果…… */
		if (fc_isNull)
			elog(ERROR, "ctid is NULL");
		fc_ctid = (ItemPointer) DatumGetPointer(fc_datum);
	}

	/* 将准备语句所需的参数转换为文本形式 */
	fc_p_values = fc_convert_prep_stmt_params(fc_fmstate, fc_ctid, fc_slots, *fc_numSlots);

	/*
	 * 执行准备好的语句。
	 */
	if (!PQsendQueryPrepared(fc_fmstate->conn,
							 fc_fmstate->p_name,
							 fc_fmstate->p_nums * (*fc_numSlots),
							 fc_p_values,
							 NULL,
							 NULL,
							 0))
		pgfdw_report_error(ERROR, NULL, fc_fmstate->conn, false, fc_fmstate->query);

	/*
	 * 获取结果，并检查成功。
	 *
	 * 我们在这里不使用 PG_TRY 块，因此请小心不要在未释放
	 * PGresult 的情况下抛出错误。
	 */
	fc_res = pgfdw_get_result(fc_fmstate->conn, fc_fmstate->query);
	if (PQresultStatus(fc_res) !=
		(fc_fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
		pgfdw_report_error(ERROR, fc_res, fc_fmstate->conn, true, fc_fmstate->query);

	/* 检查受影响的行数，如果有，则获取RETURNING元组 */
	if (fc_fmstate->has_returning)
	{
		Assert(*fc_numSlots == 1);
		fc_n_rows = PQntuples(fc_res);
		if (fc_n_rows > 0)
			fc_store_returning_result(fc_fmstate, fc_slots[0], fc_res);
	}
	else
		fc_n_rows = atoi(PQcmdTuples(fc_res));

	/* 并进行清理 */
	PQclear(fc_res);

	MemoryContextReset(fc_fmstate->temp_cxt);

	*fc_numSlots = fc_n_rows;

	/*
	 * 如果在远程端没有插入/更新/删除任何内容，则返回NULL
	 */
	return (fc_n_rows > 0) ? fc_slots : NULL;
}

/*
 * prepare_foreign_modify
 *		为INSERT/UPDATE/DELETE的执行建立准备语句
 */
static void fc_prepare_foreign_modify(PgFdwModifyState *fc_fmstate)
{
	char		fc_prep_name[NAMEDATALEN];
	char	   *fc_p_name;
	PGresult   *fc_res;

	/*
	 * 调用者如果有挂起的异步请求，应该已经处理过，
	 * 因此这里无需再次处理。
	 */

	/* 构建我们将用于准备语句的名称。 */
	snprintf(fc_prep_name, sizeof(fc_prep_name), "pgsql_fdw_prep_%u",
			 GetPrepStmtNumber(fc_fmstate->conn));
	fc_p_name = pstrdup(fc_prep_name);

	/*
	 * 我们故意不在这里指定参数类型，而是让
	 * 远程服务器默认推导。这避免了可能的问题，
	 * 因为远程服务器使用的类型OID可能与我们不同。该模块中使用的
	 * 所有准备语句都足够简单，以至于远程服务器会做出正确的选择。
	 */
	if (!PQsendPrepare(fc_fmstate->conn,
					   fc_p_name,
					   fc_fmstate->query,
					   0,
					   NULL))
		pgfdw_report_error(ERROR, NULL, fc_fmstate->conn, false, fc_fmstate->query);

	/*
	 * 获取结果，并检查成功。
	 *
	 * 我们在这里不使用 PG_TRY 块，因此请小心不要在未释放
	 * PGresult 的情况下抛出错误。
	 */
	fc_res = pgfdw_get_result(fc_fmstate->conn, fc_fmstate->query);
	if (PQresultStatus(fc_res) != PGRES_COMMAND_OK)
		pgfdw_report_error(ERROR, fc_res, fc_fmstate->conn, true, fc_fmstate->query);
	PQclear(fc_res);

	/* 此操作表明准备已经完成。 */
	fc_fmstate->p_name = fc_p_name;
}


/*
 * convert_prep_stmt_params
 *		创建表示参数值的文本字符串数组
 *
 * tupleid 是要发送的 ctid，或者为 NULL 如果没有
 * slot 是用于获取剩余参数的 slot，或者为 NULL 如果没有
 *
 * 数据在 temp_cxt 中构建；调用者使用后应重置它。
 */
static const char **
fc_convert_prep_stmt_params(PgFdwModifyState *fc_fmstate,
						 ItemPointer fc_tupleid,
						 TupleTableSlot **fc_slots,
						 int fc_numSlots)
{
	const char **fc_p_values;
	int			fc_i;
	int			fc_j;
	int			fc_pindex = 0;
	MemoryContext fc_oldcontext;

	fc_oldcontext = MemoryContextSwitchTo(fc_fmstate->temp_cxt);

	fc_p_values = (const char **) palloc(sizeof(char *) * fc_fmstate->p_nums * fc_numSlots);

	/* ctid 仅在 UPDATE/DELETE 中提供，不允许批处理 */
	Assert(!(fc_tupleid != NULL && fc_numSlots > 1));

	/* 第一个参数应为 ctid，如果它正在使用 */
	if (fc_tupleid != NULL)
	{
		Assert(fc_numSlots == 1);
		/* 对于 TID 输出不需要设置传输模式 */
		fc_p_values[fc_pindex] = OutputFunctionCall(&fc_fmstate->p_flinfo[fc_pindex],
											  PointerGetDatum(fc_tupleid));
		fc_pindex++;
	}

	/* 从 slots 获取后续参数 */
	if (fc_slots != NULL && fc_fmstate->target_attrs != NIL)
	{
		TupleDesc	fc_tupdesc = RelationGetDescr(fc_fmstate->rel);
		int			fc_nestlevel;
		ListCell   *fc_lc;

		fc_nestlevel = set_transmission_modes();

		for (fc_i = 0; fc_i < fc_numSlots; fc_i++)
		{
			fc_j = (fc_tupleid != NULL) ? 1 : 0;
			foreach(fc_lc, fc_fmstate->target_attrs)
			{
				int			fc_attnum = lfirst_int(fc_lc);
				Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_attnum - 1);
				Datum		fc_value;
				bool		fc_isnull;

				/* 忽略生成的列；它们被设置为DEFAULT */
				if (fc_attr->attgenerated)
					continue;
				fc_value = slot_getattr(fc_slots[fc_i], fc_attnum, &fc_isnull);
				if (fc_isnull)
					fc_p_values[fc_pindex] = NULL;
				else
					fc_p_values[fc_pindex] = OutputFunctionCall(&fc_fmstate->p_flinfo[fc_j],
														  fc_value);
				fc_pindex++;
				fc_j++;
			}
		}

		reset_transmission_modes(fc_nestlevel);
	}

	Assert(fc_pindex == fc_fmstate->p_nums * fc_numSlots);

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_p_values;
}

/*
 * store_returning_result
 *		存储 RETURNING 子句的结果
 *
 * 出现错误时，确保在离开时释放 PGresult。调用者没有 PG_TRY 块来确保这发生。
 */
static void fc_store_returning_result(PgFdwModifyState *fc_fmstate,
					   TupleTableSlot *fc_slot, PGresult *fc_res)
{
	PG_TRY();
	{
		HeapTuple	fc_newtup;

		fc_newtup = fc_make_tuple_from_result_row(fc_res, 0,
											fc_fmstate->rel,
											fc_fmstate->attinmeta,
											fc_fmstate->retrieved_attrs,
											NULL,
											fc_fmstate->temp_cxt);

		/*
		 * 返回的 slot 不一定适合直接存储 heaptuples，因此允许转换。
		 */
		ExecForceStoreHeapTuple(fc_newtup, fc_slot, true);
	}
	PG_CATCH();
	{
		if (fc_res)
			PQclear(fc_res);
		PG_RE_THROW();
	}
	PG_END_TRY();
}

/*
 * finish_foreign_modify
 *		释放外部插入/更新/删除操作的资源
 */
static void fc_finish_foreign_modify(PgFdwModifyState *fc_fmstate)
{
	Assert(fc_fmstate != NULL);

	/* 如果我们创建了一个准备好的语句，销毁它 */
	fc_deallocate_query(fc_fmstate);

	/* 释放远程连接 */
	ReleaseConnection(fc_fmstate->conn);
	fc_fmstate->conn = NULL;
}

/*
 * deallocate_query
 *		为外部插入/更新/删除操作解除准备好的语句
 */
static void fc_deallocate_query(PgFdwModifyState *fc_fmstate)
{
	char		fc_sql[64];
	PGresult   *fc_res;

	/* 如果查询未分配，则不做任何操作 */
	if (!fc_fmstate->p_name)
		return;

	snprintf(fc_sql, sizeof(fc_sql), "DEALLOCATE %s", fc_fmstate->p_name);

	/*
	 * 我们在这里不使用 PG_TRY 块，所以要小心不要在不释放 PGresult 的
	 * 情况下抛出错误。
	 */
	fc_res = pgfdw_exec_query(fc_fmstate->conn, fc_sql, fc_fmstate->conn_state);
	if (PQresultStatus(fc_res) != PGRES_COMMAND_OK)
		pgfdw_report_error(ERROR, fc_res, fc_fmstate->conn, true, fc_sql);
	PQclear(fc_res);
	pfree(fc_fmstate->p_name);
	fc_fmstate->p_name = NULL;
}

/*
 * build_remote_returning
 *		构建用于直接在联接上执行 UPDATE/DELETE .. RETURNING 的远程查询的 RETURNING 目标列表
 */
static List * fc_build_remote_returning(Index fc_rtindex, Relation fc_rel, List *fc_returningList)
{
	bool		fc_have_wholerow = false;
	List	   *fc_tlist = NIL;
	List	   *fc_vars;
	ListCell   *fc_lc;

	Assert(fc_returningList);

	fc_vars = pull_var_clause((Node *) fc_returningList, PVC_INCLUDE_PLACEHOLDERS);

	/*
	 * 如果有对目标关系的整行引用，则我们需要该关系的所有列。
	 */
	foreach(fc_lc, fc_vars)
	{
		Var		   *fc_var = (Var *) lfirst(fc_lc);

		if (IsA(fc_var, Var) &&
			fc_var->varno == fc_rtindex &&
			fc_var->varattno == InvalidAttrNumber)
		{
			fc_have_wholerow = true;
			break;
		}
	}

	if (fc_have_wholerow)
	{
		TupleDesc	fc_tupdesc = RelationGetDescr(fc_rel);
		int			fc_i;

		for (fc_i = 1; fc_i <= fc_tupdesc->natts; fc_i++)
		{
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_i - 1);
			Var		   *fc_var;

			/*
	 * 如果需要，添加 ctid。我们目前不支持检索任何其他
	 * 系统列。
	 */
			if (fc_attr->attisdropped)
				continue;

			fc_var = makeVar(fc_rtindex,
						  fc_i,
						  fc_attr->atttypid,
						  fc_attr->atttypmod,
						  fc_attr->attcollation,
						  0);

			fc_tlist = lappend(fc_tlist,
							makeTargetEntry((Expr *) fc_var,
											list_length(fc_tlist) + 1,
											NULL,
											false));
		}
	}

	/* 现在将任何剩余列添加到 tlist。 */
	foreach(fc_lc, fc_vars)
	{
		Var		   *fc_var = (Var *) lfirst(fc_lc);

		/*
		 * 对目标关系没有整行引用的必要。我们不需要除 ctid 和 oid 之外的系统列，因为这些是在本地设置的。
		 */
		if (IsA(fc_var, Var) &&
			fc_var->varno == fc_rtindex &&
			fc_var->varattno <= InvalidAttrNumber &&
			fc_var->varattno != SelfItemPointerAttributeNumber)
			continue;			/* 不需要它 */

		if (tlist_member((Expr *) fc_var, fc_tlist))
			continue;			/* 已经得到了 */

		fc_tlist = lappend(fc_tlist,
						makeTargetEntry((Expr *) fc_var,
										list_length(fc_tlist) + 1,
										NULL,
										false));
	}

	list_free(fc_vars);

	return fc_tlist;
}

/*
 * rebuild_fdw_scan_tlist
 *		从给定的 tlist 构建给定外部扫描计划节点的新 fdw_scan_tlist
 *
 * 给定外部扫描计划节点的 fdw_scan_tlist 可能包含给定 tlist 不包含的列。fdw_scan_tlist 可能包含目标关系的 resjunk 列，如 'ctid' 和非目标关系的 'wholerow'，但 tlist 可能不包含它们，例如。因此，调整 tlist 以包含 fdw_scan_tlist 中指定的所有列；否则 setrefs.c 会感到困惑。
 */
static void fc_rebuild_fdw_scan_tlist(ForeignScan *fc_fscan, List *fc_tlist)
{
	List	   *fc_new_tlist = fc_tlist;
	List	   *fc_old_tlist = fc_fscan->fdw_scan_tlist;
	ListCell   *fc_lc;

	foreach(fc_lc, fc_old_tlist)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_lc);

		if (tlist_member(fc_tle->expr, fc_new_tlist))
			continue;			/* 已经得到了 */

		fc_new_tlist = lappend(fc_new_tlist,
							makeTargetEntry(fc_tle->expr,
											list_length(fc_new_tlist) + 1,
											NULL,
											false));
	}
	fc_fscan->fdw_scan_tlist = fc_new_tlist;
}

/*
 * 执行直接的 UPDATE/DELETE 语句。
 */
static void fc_execute_dml_stmt(ForeignScanState *fc_node)
{
	PgFdwDirectModifyState *fc_dmstate = (PgFdwDirectModifyState *) fc_node->fdw_state;
	ExprContext *fc_econtext = fc_node->ss.ps.ps_ExprContext;
	int			fc_numParams = fc_dmstate->numParams;
	const char **fc_values = fc_dmstate->param_values;

	/* 首先，处理任何待处理的异步请求。 */
	if (fc_dmstate->conn_state->pendingAreq)
		process_pending_request(fc_dmstate->conn_state->pendingAreq);

	/*
	 * 构建文本格式的查询参数值数组。
	 */

	if (fc_numParams > 0)
		fc_process_query_params(fc_econtext,
							 fc_dmstate->param_flinfo,
							 fc_dmstate->param_exprs,
							 fc_values);

	/*
	 * 注意，我们为 paramTypes 传递 NULL，从而强制远程服务器
	 * 推断所有参数的类型。由于我们显式地转换每个
	 * 参数（见 deparse.c），因此“推断”是微不足道的，
	 * 将产生期望的结果。这使我们能够避免假设远程
	 * 服务器有与我们相同的参数类型 OID。
	 */
	if (!PQsendQueryParams(fc_dmstate->conn, fc_dmstate->query, fc_numParams,
						   NULL, fc_values, NULL, NULL, 0))
		pgfdw_report_error(ERROR, NULL, fc_dmstate->conn, false, fc_dmstate->query);

	/*
	 * 获取结果，并检查成功。
	 *
	 * 我们在这里不使用 PG_TRY 块，因此请小心不要在未释放
	 * PGresult 的情况下抛出错误。
	 */
	fc_dmstate->result = pgfdw_get_result(fc_dmstate->conn, fc_dmstate->query);
	if (PQresultStatus(fc_dmstate->result) !=
		(fc_dmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
		pgfdw_report_error(ERROR, fc_dmstate->result, fc_dmstate->conn, true,
						   fc_dmstate->query);

	/* 获取受影响的行数。 */
	if (fc_dmstate->has_returning)
		fc_dmstate->num_tuples = PQntuples(fc_dmstate->result);
	else
		fc_dmstate->num_tuples = atoi(PQcmdTuples(fc_dmstate->result));
}

/*
 * 获取 RETURNING 子句的结果。
 */
static TupleTableSlot * fc_get_returning_data(ForeignScanState *fc_node)
{
	PgFdwDirectModifyState *fc_dmstate = (PgFdwDirectModifyState *) fc_node->fdw_state;
	EState	   *fc_estate = fc_node->ss.ps.state;
	ResultRelInfo *fc_resultRelInfo = fc_node->resultRelInfo;
	TupleTableSlot *fc_slot = fc_node->ss.ss_ScanTupleSlot;
	TupleTableSlot *fc_resultSlot;

	Assert(fc_resultRelInfo->ri_projectReturning);

	/* 如果我们没有获得任何元组，则必须是数据的结束。 */
	if (fc_dmstate->next_tuple >= fc_dmstate->num_tuples)
		return ExecClearTuple(fc_slot);

	/* 如果需要，增加命令 es_processed 计数。 */
	if (fc_dmstate->set_processed)
		fc_estate->es_processed += 1;

	/*
	 * 存储一个 RETURNING 元组。如果 has_returning 为 false，则只发出一个虚拟
	 * 元组。（当本地查询的形式为“UPDATE/DELETE .. RETURNING 1”时，has_returning 为 false。）
	 */
	if (!fc_dmstate->has_returning)
	{
		ExecStoreAllNullTuple(fc_slot);
		fc_resultSlot = fc_slot;
	}
	else
	{
		/*
		 * 在出现错误时，确保在退出时释放 PGresult。调用者
		 * 没有 PG_TRY 块来确保这种情况发生。
		 */
		PG_TRY();
		{
			HeapTuple	fc_newtup;

			fc_newtup = fc_make_tuple_from_result_row(fc_dmstate->result,
												fc_dmstate->next_tuple,
												fc_dmstate->rel,
												fc_dmstate->attinmeta,
												fc_dmstate->retrieved_attrs,
												fc_node,
												fc_dmstate->temp_cxt);
			ExecStoreHeapTuple(fc_newtup, fc_slot, false);
		}
		PG_CATCH();
		{
			if (fc_dmstate->result)
				PQclear(fc_dmstate->result);
			PG_RE_THROW();
		}
		PG_END_TRY();

		/* 获取更新/删除的元组。 */
		if (fc_dmstate->rel)
			fc_resultSlot = fc_slot;
		else
			fc_resultSlot = fc_apply_returning_filter(fc_dmstate, fc_resultRelInfo, fc_slot, fc_estate);
	}
	fc_dmstate->next_tuple++;

	/* 为本地查询的 RETURNING 列表的评估提供可用的插槽。 */
	fc_resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple =
		fc_resultSlot;

	return fc_slot;
}

/*
 * 初始化一个过滤器，从扫描元组中提取更新/删除的元组。
 */
static void fc_init_returning_filter(PgFdwDirectModifyState *fc_dmstate,
					  List *fc_fdw_scan_tlist,
					  Index fc_rtindex)
{
	TupleDesc	fc_resultTupType = RelationGetDescr(fc_dmstate->resultRel);
	ListCell   *fc_lc;
	int			fc_i;

	/*
	 * 计算 fdw_scan_tlist 条目的映射与
	 * 结果元组的属性之间的关系。
	 *
	 * “map”是结果元组属性在
	 * fdw_scan_tlist 中的索引数组，即结果
	 * 元组的每个属性对应一个条目。对于在该列表中
	 * 没有对应条目的属性，我们存储零，标记结果元组中需要 NULL。
	 *
	 * 如果有的话，还获取 ctid 和 oid 的条目的索引。
	 */
	fc_dmstate->attnoMap = (AttrNumber *)
		palloc0(fc_resultTupType->natts * sizeof(AttrNumber));

	fc_dmstate->ctidAttno = fc_dmstate->oidAttno = 0;

	fc_i = 1;
	fc_dmstate->hasSystemCols = false;
	foreach(fc_lc, fc_fdw_scan_tlist)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_lc);
		Var		   *fc_var = (Var *) fc_tle->expr;

		Assert(IsA(fc_var, Var));

		/*
		 * 如果 Var 是从外部服务器检索的目标关系的列，请获取条目的索引。
		 */
		if (fc_var->varno == fc_rtindex &&
			list_member_int(fc_dmstate->retrieved_attrs, fc_i))
		{
			int			fc_attrno = fc_var->varattno;

			if (fc_attrno < 0)
			{
				/*
				 * 我们不检索除了 ctid 和 oid 外的系统列。
				 */
				if (fc_attrno == SelfItemPointerAttributeNumber)
					fc_dmstate->ctidAttno = fc_i;
				else
					Assert(false);
				fc_dmstate->hasSystemCols = true;
			}
			else
			{
				/*
				 * 我们也不检索对目标
				 * 关系的整行引用。
				 */
				Assert(fc_attrno > 0);

				fc_dmstate->attnoMap[fc_attrno - 1] = fc_i;
			}
		}
		fc_i++;
	}
}

/*
 * 从扫描元组中提取并返回更新/删除的元组。
 */
static TupleTableSlot * fc_apply_returning_filter(PgFdwDirectModifyState *fc_dmstate,
					   ResultRelInfo *fc_resultRelInfo,
					   TupleTableSlot *fc_slot,
					   EState *fc_estate)
{
	TupleDesc	fc_resultTupType = RelationGetDescr(fc_dmstate->resultRel);
	TupleTableSlot *fc_resultSlot;
	Datum	   *fc_values;
	bool	   *fc_isnull;
	Datum	   *fc_old_values;
	bool	   *fc_old_isnull;
	int			fc_i;

	/*
	 * 使用返回元组插槽作为存储结果元组的地方。
	 */
	fc_resultSlot = ExecGetReturningSlot(fc_estate, fc_resultRelInfo);

	/*
	 * 提取扫描元组的所有值。
	 */
	slot_getallattrs(fc_slot);
	fc_old_values = fc_slot->tts_values;
	fc_old_isnull = fc_slot->tts_isnull;

	/*
	 * 准备构建结果元组。
	 */
	ExecClearTuple(fc_resultSlot);
	fc_values = fc_resultSlot->tts_values;
	fc_isnull = fc_resultSlot->tts_isnull;

	/*
	 * 将数据转置到结果元组的适当字段中。
	 */
	for (fc_i = 0; fc_i < fc_resultTupType->natts; fc_i++)
	{
		int			fc_j = fc_dmstate->attnoMap[fc_i];

		if (fc_j == 0)
		{
			fc_values[fc_i] = (Datum) 0;
			fc_isnull[fc_i] = true;
		}
		else
		{
			fc_values[fc_i] = fc_old_values[fc_j - 1];
			fc_isnull[fc_i] = fc_old_isnull[fc_j - 1];
		}
	}

	/*
	 * 构建虚拟元组。
	 */
	ExecStoreVirtualTuple(fc_resultSlot);

	/*
	 * 如果我们有任何系统列要返回， 在
	 * 插槽中根据上述列值生成堆元组，并在
	 * 该元组中安装系统列。
	 */
	if (fc_dmstate->hasSystemCols)
	{
		HeapTuple	fc_resultTup = ExecFetchSlotHeapTuple(fc_resultSlot, true, NULL);

		/* ctid */
		if (fc_dmstate->ctidAttno)
		{
			ItemPointer fc_ctid = NULL;

			fc_ctid = (ItemPointer) DatumGetPointer(fc_old_values[fc_dmstate->ctidAttno - 1]);
			fc_resultTup->t_self = *fc_ctid;
		}

		/*
		 * 以及剩余的列
		 *
		 * 注意：由于我们目前不允许目标关系出现在外部连接的可空侧，因此任何系统列都不会为 NULL。
		 *
		 * 注意：此处无需关心 tableoid，因为它将在 ExecProcessReturning() 中初始化。
		 */
		HeapTupleHeaderSetXmin(fc_resultTup->t_data, InvalidTransactionId);
		HeapTupleHeaderSetXmax(fc_resultTup->t_data, InvalidTransactionId);
		HeapTupleHeaderSetCmin(fc_resultTup->t_data, InvalidTransactionId);
	}

	/*
	 * 并返回结果元组。
	 */
	return fc_resultSlot;
}

/*
 * 为远程查询中使用的参数处理做准备。
 */
static void fc_prepare_query_params(PlanState *fc_node,
					 List *fc_fdw_exprs,
					 int fc_numParams,
					 FmgrInfo **fc_param_flinfo,
					 List **fc_param_exprs,
					 const char ***fc_param_values)
{
	int			fc_i;
	ListCell   *fc_lc;

	Assert(fc_numParams > 0);

	/* 为远程查询中使用的参数的输出转换做准备。 */
	*fc_param_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * fc_numParams);

	fc_i = 0;
	foreach(fc_lc, fc_fdw_exprs)
	{
		Node	   *fc_param_expr = (Node *) lfirst(fc_lc);
		Oid			fc_typefnoid;
		bool		fc_isvarlena;

		getTypeOutputInfo(exprType(fc_param_expr), &fc_typefnoid, &fc_isvarlena);
		fmgr_info(fc_typefnoid, &(*fc_param_flinfo)[fc_i]);
		fc_i++;
	}

	/*
	 * 准备远程参数表达式进行评估。 （注意：在实践中，我们期望所有这些表达式只是 Params，因此我们可能可以做一些比使用完整的表达式评估机制更高效的事情。 但可能几乎没有好处，并且这将要求 postgres_fdw 了解有关 Param 评估的更多信息，这并不理想。）
	 */
	*fc_param_exprs = ExecInitExprList(fc_fdw_exprs, fc_node);

	/* 为查询参数的文本形式分配缓冲区。 */
	*fc_param_values = (const char **) palloc0(fc_numParams * sizeof(char *));
}

/*
 * 构造文本格式的查询参数值数组。
 */
static void fc_process_query_params(ExprContext *fc_econtext,
					 FmgrInfo *fc_param_flinfo,
					 List *fc_param_exprs,
					 const char **fc_param_values)
{
	int			fc_nestlevel;
	int			fc_i;
	ListCell   *fc_lc;

	fc_nestlevel = set_transmission_modes();

	fc_i = 0;
	foreach(fc_lc, fc_param_exprs)
	{
		ExprState  *fc_expr_state = (ExprState *) lfirst(fc_lc);
		Datum		fc_expr_value;
		bool		fc_isNull;

		/* 评估参数表达式 */
		fc_expr_value = ExecEvalExpr(fc_expr_state, fc_econtext, &fc_isNull);

		/*
		 * 通过调用特定类型的输出函数获取每个参数值的字符串表示，除非该值为 null。
		 */
		if (fc_isNull)
			fc_param_values[fc_i] = NULL;
		else
			fc_param_values[fc_i] = OutputFunctionCall(&fc_param_flinfo[fc_i], fc_expr_value);

		fc_i++;
	}

	reset_transmission_modes(fc_nestlevel);
}

/*
 * postgresAnalyzeForeignTable
 *		测试是否支持分析此外部表
 */
static bool fc_postgresAnalyzeForeignTable(Relation fc_relation,
							AcquireSampleRowsFunc *fc_func,
							BlockNumber *fc_totalpages)
{
	ForeignTable *fc_table;
	UserMapping *fc_user;
	PGconn	   *fc_conn;
	StringInfoData fc_sql;
	PGresult   *volatile fc_res = NULL;

	/* 返回行分析函数指针 */
	*fc_func = fc_postgresAcquireSampleRowsFunc;

	/*
	 * 现在我们必须获取页面数。 分析 API 要求我们现在返回这个结果，这让人恼火，因为它强迫我们在这个例程和 postgresAcquireSampleRowsFunc 之间重复一些努力。 但此时重新定义那个 API 可能不值得。
	 */

	/*
	 * 获取要使用的连接。 我们以表的拥有者身份进行远程访问，即使分析是由其他用户启动的。
	 */
	fc_table = GetForeignTable(RelationGetRelid(fc_relation));
	fc_user = GetUserMapping(fc_relation->rd_rel->relowner, fc_table->serverid);
	fc_conn = GetConnection(fc_user, false, NULL);

	/*
	 * 构造获取关系页面计数的命令。
	 */
	initStringInfo(&fc_sql);
	deparseAnalyzeSizeSql(&fc_sql, fc_relation);

	/* 在以下过程中，不要冒险泄露任何 PGresults。 */
	PG_TRY();
	{
		fc_res = pgfdw_exec_query(fc_conn, fc_sql.data, NULL);
		if (PQresultStatus(fc_res) != PGRES_TUPLES_OK)
			pgfdw_report_error(ERROR, fc_res, fc_conn, false, fc_sql.data);

		if (PQntuples(fc_res) != 1 || PQnfields(fc_res) != 1)
			elog(ERROR, "unexpected result from deparseAnalyzeSizeSql query");
		*fc_totalpages = strtoul(PQgetvalue(fc_res, 0, 0), NULL, 10);
	}
	PG_FINALLY();
	{
		if (fc_res)
			PQclear(fc_res);
	}
	PG_END_TRY();

	ReleaseConnection(fc_conn);

	return true;
}

/*
 * 从由 postgres_fdw 管理的外部表中获取随机样本行。
 *
 * 我们从远程端获取整个表并挑选出一些样本行。
 *
 * 选择的行以调用者分配的数组 rows[] 返回，
 * 该数组必须至少拥有 targrows 个条目。
 * 选择的实际行数作为函数结果返回。
 * 我们还统计表中总行数并将其返回到 
 * *totalrows。 注意 *totaldeadrows 始终设置为 0。
 *
 * 注意，返回的行列表并不总是按表中的物理位置排序。因此，稍后派生的相关性估算可能毫无意义，但这没关系，因为我们当前没有使用估算（规划器仅关注索引扫描的相关性）。
 */
static int fc_postgresAcquireSampleRowsFunc(Relation fc_relation, int fc_elevel,
							  HeapTuple *fc_rows, int fc_targrows,
							  double *fc_totalrows,
							  double *fc_totaldeadrows)
{
	PgFdwAnalyzeState fc_astate;
	ForeignTable *fc_table;
	ForeignServer *fc_server;
	UserMapping *fc_user;
	PGconn	   *fc_conn;
	unsigned int fc_cursor_number;
	StringInfoData fc_sql;
	PGresult   *volatile fc_res = NULL;

	/* 初始化工作空间状态 */
	fc_astate.rel = fc_relation;
	fc_astate.attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(fc_relation));

	fc_astate.rows = fc_rows;
	fc_astate.targrows = fc_targrows;
	fc_astate.numrows = 0;
	fc_astate.samplerows = 0;
	fc_astate.rowstoskip = -1;		/* -1 意味着尚未设置 */
	reservoir_init_selection_state(&fc_astate.rstate, fc_targrows);

	/* 记住分析上下文，并为每个元组创建一个临时上下文 */
	fc_astate.anl_cxt = CurrentMemoryContext;
	fc_astate.temp_cxt = AllocSetContextCreate(CurrentMemoryContext,
											"postgres_fdw temporary data",
											ALLOCSET_SMALL_SIZES);

	/*
	 * 获取要使用的连接。 我们以表的拥有者身份进行远程访问，即使分析是由其他用户启动的。
	 */
	fc_table = GetForeignTable(RelationGetRelid(fc_relation));
	fc_server = GetForeignServer(fc_table->serverid);
	fc_user = GetUserMapping(fc_relation->rd_rel->relowner, fc_table->serverid);
	fc_conn = GetConnection(fc_user, false, NULL);

	/*
	 * 构造游标以从远程检索完整行。
	 */
	fc_cursor_number = GetCursorNumber(fc_conn);
	initStringInfo(&fc_sql);
	appendStringInfo(&fc_sql, "DECLARE c%u CURSOR FOR ", fc_cursor_number);
	deparseAnalyzeSql(&fc_sql, fc_relation, &fc_astate.retrieved_attrs);

	/* 在以下过程中，不要冒险泄露任何 PGresults。 */
	PG_TRY();
	{
		char		fc_fetch_sql[64];
		int			fc_fetch_size;
		ListCell   *fc_lc;

		fc_res = pgfdw_exec_query(fc_conn, fc_sql.data, NULL);
		if (PQresultStatus(fc_res) != PGRES_COMMAND_OK)
			pgfdw_report_error(ERROR, fc_res, fc_conn, false, fc_sql.data);
		PQclear(fc_res);
		fc_res = NULL;

		/*
		 * 确定获取的大小。默认值是任意的，但不应过大。
		 */
		fc_fetch_size = 100;
		foreach(fc_lc, fc_server->options)
		{
			DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

			if (strcmp(fc_def->defname, "fetch_size") == 0)
			{
				(void) parse_int(defGetString(fc_def), &fc_fetch_size, 0, NULL);
				break;
			}
		}
		foreach(fc_lc, fc_table->options)
		{
			DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

			if (strcmp(fc_def->defname, "fetch_size") == 0)
			{
				(void) parse_int(defGetString(fc_def), &fc_fetch_size, 0, NULL);
				break;
			}
		}

		/* 构造从远程获取行的命令。 */
		snprintf(fc_fetch_sql, sizeof(fc_fetch_sql), "FETCH %d FROM c%u",
				 fc_fetch_size, fc_cursor_number);

		/* 一次检索并处理一批行。 */
		for (;;)
		{
			int			fc_numrows;
			int			fc_i;

			/* 允许用户取消长查询 */
			CHECK_FOR_INTERRUPTS();

			/*
			 * XXX 可能的未来改进：如果 rowstoskip 很大，我们
			 * 可以发行 MOVE，而不是物理获取行，
			 * 然后只需适当地调整 rowstoskip 和 samplerows。
			 */

			/* 获取一些行 */
			fc_res = pgfdw_exec_query(fc_conn, fc_fetch_sql, NULL);
			/* 出错时，报告原始查询，而不是 FETCH。 */
			if (PQresultStatus(fc_res) != PGRES_TUPLES_OK)
				pgfdw_report_error(ERROR, fc_res, fc_conn, false, fc_sql.data);

			/* 处理我们得到的任何内容。 */
			fc_numrows = PQntuples(fc_res);
			for (fc_i = 0; fc_i < fc_numrows; fc_i++)
				fc_analyze_row_processor(fc_res, fc_i, &fc_astate);

			PQclear(fc_res);
			fc_res = NULL;

			/* 如果我们没有获取所有请求的行，则必须是 EOF。 */
			if (fc_numrows < fc_fetch_size)
				break;
		}

		/* 关闭游标，只是为了整洁。 */
		fc_close_cursor(fc_conn, fc_cursor_number, NULL);
	}
	PG_CATCH();
	{
		if (fc_res)
			PQclear(fc_res);
		PG_RE_THROW();
	}
	PG_END_TRY();

	ReleaseConnection(fc_conn);

	/* 我们假设没有死元组。 */
	*fc_totaldeadrows = 0.0;

	/* 我们已从外部服务器检索到所有活动元组。 */
	*fc_totalrows = fc_astate.samplerows;

	/*
	 * 输出一些有趣的关系信息
	 */
	ereport(fc_elevel,
			(errmsg("\"%s\": table contains %.0f rows, %d rows in sample",
					RelationGetRelationName(fc_relation),
					fc_astate.samplerows, fc_astate.numrows)));

	return fc_astate.numrows;
}

/*
 * 从查询结果中收集样本行。
 *	 - 在收集到目标样本数量之前，使用样本中的所有元组。
 *	 - 随后，随机替换已经采样的元组。
 */
static void fc_analyze_row_processor(PGresult *fc_res, int fc_row, PgFdwAnalyzeState *fc_astate)
{
	int			fc_targrows = fc_astate->targrows;
	int			fc_pos;			/* 用于存储元组的数组索引 */
	MemoryContext fc_oldcontext;

	/* 始终递增样本行计数器。 */
	fc_astate->samplerows += 1;

	/*
	 * 确定此样本行应存储的槽位。将 pos 设置为负值以指示应跳过该行。
	 */
	if (fc_astate->numrows < fc_targrows)
	{
		/* 前 targrows 行始终包含在样本中 */
		fc_pos = fc_astate->numrows++;
	}
	else
	{
		/*
		 * 现在我们开始替换样本中的元组，直到到达关系的末尾。
		 * 与 analyze.c 中的 acquire_sample_rows 中的算法相同；
		 * 参见 Jeff Vitter 的论文。
		 */
		if (fc_astate->rowstoskip < 0)
			fc_astate->rowstoskip = reservoir_get_next_S(&fc_astate->rstate, fc_astate->samplerows, fc_targrows);

		if (fc_astate->rowstoskip <= 0)
		{
			/* 选择一个随机的储备元素进行替换。 */
			fc_pos = (int) (fc_targrows * sampler_random_fract(&fc_astate->rstate.randstate));
			Assert(fc_pos >= 0 && fc_pos < fc_targrows);
			heap_freetuple(fc_astate->rows[fc_pos]);
		}
		else
		{
			/* 跳过这个元组。 */
			fc_pos = -1;
		}

		fc_astate->rowstoskip -= 1;
	}

	if (fc_pos >= 0)
	{
		/*
		 * 从当前结果行创建样本元组，并将其存储在上述确定的位置。
		 * 元组必须在 anl_cxt 中创建。
		 */
		fc_oldcontext = MemoryContextSwitchTo(fc_astate->anl_cxt);

		fc_astate->rows[fc_pos] = fc_make_tuple_from_result_row(fc_res, fc_row,
													   fc_astate->rel,
													   fc_astate->attinmeta,
													   fc_astate->retrieved_attrs,
													   NULL,
													   fc_astate->temp_cxt);

		MemoryContextSwitchTo(fc_oldcontext);
	}
}

/*
 * 导入外部模式
 */
static List * fc_postgresImportForeignSchema(ImportForeignSchemaStmt *fc_stmt, Oid fc_serverOid)
{
	List	   *fc_commands = NIL;
	bool		fc_import_collate = true;
	bool		fc_import_default = false;
	bool		fc_import_generated = true;
	bool		fc_import_not_null = true;
	ForeignServer *fc_server;
	UserMapping *fc_mapping;
	PGconn	   *fc_conn;
	StringInfoData fc_buf;
	PGresult   *volatile fc_res = NULL;
	int			fc_numrows,
				fc_i;
	ListCell   *fc_lc;

	/* 解析语句选项 */
	foreach(fc_lc, fc_stmt->options)
	{
		DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

		if (strcmp(fc_def->defname, "import_collate") == 0)
			fc_import_collate = defGetBoolean(fc_def);
		else if (strcmp(fc_def->defname, "import_default") == 0)
			fc_import_default = defGetBoolean(fc_def);
		else if (strcmp(fc_def->defname, "import_generated") == 0)
			fc_import_generated = defGetBoolean(fc_def);
		else if (strcmp(fc_def->defname, "import_not_null") == 0)
			fc_import_not_null = defGetBoolean(fc_def);
		else
			ereport(ERROR,
					(errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
					 errmsg("invalid option \"%s\"", fc_def->defname)));
	}

	/*
	 * 获取与外部服务器的连接。 连接管理器将
	 * 在必要时建立新的连接。
	 */
	fc_server = GetForeignServer(fc_serverOid);
	fc_mapping = GetUserMapping(GetUserId(), fc_server->serverid);
	fc_conn = GetConnection(fc_mapping, false, NULL);

	/* 如果远程服务器没有排序规则，则不要尝试导入排序规则 */
	if (PQserverVersion(fc_conn) < 90100)
		fc_import_collate = false;

	/* 为字符串创建工作区 */
	initStringInfo(&fc_buf);

	/* 在以下过程中，不要冒险泄露任何 PGresults。 */
	PG_TRY();
	{
		/* 检查模式确实存在 */
		appendStringInfoString(&fc_buf, "SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = ");
		deparseStringLiteral(&fc_buf, fc_stmt->remote_schema);

		fc_res = pgfdw_exec_query(fc_conn, fc_buf.data, NULL);
		if (PQresultStatus(fc_res) != PGRES_TUPLES_OK)
			pgfdw_report_error(ERROR, fc_res, fc_conn, false, fc_buf.data);

		if (PQntuples(fc_res) != 1)
			ereport(ERROR,
					(errcode(ERRCODE_FDW_SCHEMA_NOT_FOUND),
					 errmsg("schema \"%s\" is not present on foreign server \"%s\"",
							fc_stmt->remote_schema, fc_server->servername)));

		PQclear(fc_res);
		fc_res = NULL;
		resetStringInfo(&fc_buf);

		/*
		 * 从此模式中提取所有表数据，可能受到
		 * EXCEPT 或 LIMIT TO 的限制。 （我们实际上不需要关注
		 * EXCEPT/LIMIT TO，因为核心代码会根据这些列表过滤
		 * 我们返回的语句。不过，这样可以节省一些周期，避免
		 * 在最初处理被排除的表。）
		 *
		 * 仅在 LIMIT TO 子句中明确指定时，导入分区的表数据。
		 * 否则忽略它们，仅包括根分区表的定义，以允许在
		 * 导入的模式中本地访问完整的远程数据集。
		 *
		 * 注意：由于我们以限制为 pg_catalog 的 search_path 运行连接，
		 * format_type() 和 pg_get_expr() 的输出将始终包括
		 * 其他模式中类型/函数的模式名称，这正是我们所希望的。
		 */
		appendStringInfoString(&fc_buf,
							   "SELECT relname, "
							   "  attname, "
							   "  format_type(atttypid, atttypmod), "
							   "  attnotnull, "
							   "  pg_get_expr(adbin, adrelid), ");

		/* 自 Postgres 12 起支持生成列 */
		if (PQserverVersion(fc_conn) >= 120000)
			appendStringInfoString(&fc_buf,
								   "  attgenerated, ");
		else
			appendStringInfoString(&fc_buf,
								   "  NULL, ");

		if (fc_import_collate)
			appendStringInfoString(&fc_buf,
								   "  collname, "
								   "  collnsp.nspname ");
		else
			appendStringInfoString(&fc_buf,
								   "  NULL, NULL ");

		appendStringInfoString(&fc_buf,
							   "FROM pg_class c "
							   "  JOIN pg_namespace n ON "
							   "    relnamespace = n.oid "
							   "  LEFT JOIN pg_attribute a ON "
							   "    attrelid = c.oid AND attnum > 0 "
							   "      AND NOT attisdropped "
							   "  LEFT JOIN pg_attrdef ad ON "
							   "    adrelid = c.oid AND adnum = attnum ");

		if (fc_import_collate)
			appendStringInfoString(&fc_buf,
								   "  LEFT JOIN pg_collation coll ON "
								   "    coll.oid = attcollation "
								   "  LEFT JOIN pg_namespace collnsp ON "
								   "    collnsp.oid = collnamespace ");

		appendStringInfoString(&fc_buf,
							   "WHERE c.relkind IN ("
							   CppAsString2(RELKIND_RELATION) ","
							   CppAsString2(RELKIND_VIEW) ","
							   CppAsString2(RELKIND_FOREIGN_TABLE) ","
							   CppAsString2(RELKIND_MATVIEW) ","
							   CppAsString2(RELKIND_PARTITIONED_TABLE) ") "
							   "  AND n.nspname = ");
		deparseStringLiteral(&fc_buf, fc_stmt->remote_schema);

		/* 自 Postgres 10 起支持分区 */
		if (PQserverVersion(fc_conn) >= 100000 &&
			fc_stmt->list_type != FDW_IMPORT_SCHEMA_LIMIT_TO)
			appendStringInfoString(&fc_buf, " AND NOT c.relispartition ");

		/* 应用 LIMIT TO 和 EXCEPT 的限制 */
		if (fc_stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO ||
			fc_stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT)
		{
			bool		fc_first_item = true;

			appendStringInfoString(&fc_buf, " AND c.relname ");
			if (fc_stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT)
				appendStringInfoString(&fc_buf, "NOT ");
			appendStringInfoString(&fc_buf, "IN (");

			/* 在 IN 子句中附加表名称列表 */
			foreach(fc_lc, fc_stmt->table_list)
			{
				RangeVar   *fc_rv = (RangeVar *) lfirst(fc_lc);

				if (fc_first_item)
					fc_first_item = false;
				else
					appendStringInfoString(&fc_buf, ", ");
				deparseStringLiteral(&fc_buf, fc_rv->relname);
			}
			appendStringInfoChar(&fc_buf, ')');
		}

		/* 在查询末尾附加 ORDER BY 以确保输出排序 */
		appendStringInfoString(&fc_buf, " ORDER BY c.relname, a.attnum");

		/* 提取数据 */
		fc_res = pgfdw_exec_query(fc_conn, fc_buf.data, NULL);
		if (PQresultStatus(fc_res) != PGRES_TUPLES_OK)
			pgfdw_report_error(ERROR, fc_res, fc_conn, false, fc_buf.data);

		/* 处理结果 */
		fc_numrows = PQntuples(fc_res);
		/* 注意：i 的增量发生在内循环的 while() 测试中 */
		for (fc_i = 0; fc_i < fc_numrows;)
		{
			char	   *fc_tablename = PQgetvalue(fc_res, fc_i, 0);
			bool		fc_first_item = true;

			resetStringInfo(&fc_buf);
			appendStringInfo(&fc_buf, "CREATE FOREIGN TABLE %s (\n",
							 quote_identifier(fc_tablename));

			/* 扫描此表的所有行 */
			do
			{
				char	   *fc_attname;
				char	   *fc_typename;
				char	   *fc_attnotnull;
				char	   *fc_attgenerated;
				char	   *fc_attdefault;
				char	   *fc_collname;
				char	   *fc_collnamespace;

				/* 如果表没有列，我们将在这里看到空值 */
				if (PQgetisnull(fc_res, fc_i, 1))
					continue;

				fc_attname = PQgetvalue(fc_res, fc_i, 1);
				fc_typename = PQgetvalue(fc_res, fc_i, 2);
				fc_attnotnull = PQgetvalue(fc_res, fc_i, 3);
				fc_attdefault = PQgetisnull(fc_res, fc_i, 4) ? (char *) NULL :
					PQgetvalue(fc_res, fc_i, 4);
				fc_attgenerated = PQgetisnull(fc_res, fc_i, 5) ? (char *) NULL :
					PQgetvalue(fc_res, fc_i, 5);
				fc_collname = PQgetisnull(fc_res, fc_i, 6) ? (char *) NULL :
					PQgetvalue(fc_res, fc_i, 6);
				fc_collnamespace = PQgetisnull(fc_res, fc_i, 7) ? (char *) NULL :
					PQgetvalue(fc_res, fc_i, 7);

				if (fc_first_item)
					fc_first_item = false;
				else
					appendStringInfoString(&fc_buf, ",\n");

				/* 打印列名和类型 */
				appendStringInfo(&fc_buf, "  %s %s",
								 quote_identifier(fc_attname),
								 fc_typename);

				/*
				 * 添加 column_name 选项，以便重命名外部表的
				 * 列时不会破坏与基础列的关联。
				 */
				appendStringInfoString(&fc_buf, " OPTIONS (column_name ");
				deparseStringLiteral(&fc_buf, fc_attname);
				appendStringInfoChar(&fc_buf, ')');

				/* 如有需要，添加 COLLATE */
				if (fc_import_collate && fc_collname != NULL && fc_collnamespace != NULL)
					appendStringInfo(&fc_buf, " COLLATE %s.%s",
									 quote_identifier(fc_collnamespace),
									 quote_identifier(fc_collname));

				/* 如有需要，添加 DEFAULT */
				if (fc_import_default && fc_attdefault != NULL &&
					(!fc_attgenerated || !fc_attgenerated[0]))
					appendStringInfo(&fc_buf, " DEFAULT %s", fc_attdefault);

				/* 如有需要，添加 GENERATED */
				if (fc_import_generated && fc_attgenerated != NULL &&
					fc_attgenerated[0] == ATTRIBUTE_GENERATED_STORED)
				{
					Assert(fc_attdefault != NULL);
					appendStringInfo(&fc_buf,
									 " GENERATED ALWAYS AS (%s) STORED",
									 fc_attdefault);
				}

				/* 如有需要，添加 NOT NULL */
				if (fc_import_not_null && fc_attnotnull[0] == 't')
					appendStringInfoString(&fc_buf, " NOT NULL");
			}
			while (++fc_i < fc_numrows &&
				   strcmp(PQgetvalue(fc_res, fc_i, 0), fc_tablename) == 0);

			/*
			 * 添加服务器名称和表级选项。我们将远程
			 * 模式和表名指定为选项（后者确保
			 * 重命名外部表时不会破坏关联）。
			 */
			appendStringInfo(&fc_buf, "\n) SERVER %s\nOPTIONS (",
							 quote_identifier(fc_server->servername));

			appendStringInfoString(&fc_buf, "schema_name ");
			deparseStringLiteral(&fc_buf, fc_stmt->remote_schema);
			appendStringInfoString(&fc_buf, ", table_name ");
			deparseStringLiteral(&fc_buf, fc_tablename);

			appendStringInfoString(&fc_buf, ");");

			fc_commands = lappend(fc_commands, pstrdup(fc_buf.data));
		}
	}
	PG_FINALLY();
	{
		if (fc_res)
			PQclear(fc_res);
	}
	PG_END_TRY();

	ReleaseConnection(fc_conn);

	return fc_commands;
}

/*
 * 评估内部和外部关系之间的连接是否可以下推到
 * 外部服务器。作为副作用，保存我们在此
 * 函数中获得的信息到传入的 PgFdwRelationInfo 中。
 */
static bool fc_foreign_join_ok(PlannerInfo *fc_root, RelOptInfo *fc_joinrel, JoinType fc_jointype,
				RelOptInfo *fc_outerrel, RelOptInfo *fc_innerrel,
				JoinPathExtraData *fc_extra)
{
	PgFdwRelationInfo *fc_fpinfo;
	PgFdwRelationInfo *fc_fpinfo_o;
	PgFdwRelationInfo *fc_fpinfo_i;
	ListCell   *fc_lc;
	List	   *fc_joinclauses;

	/*
	 * 我们支持下推 INNER、LEFT、RIGHT 和 FULL OUTER 连接。
	 * 构造表示 SEMI 和 ANTI 连接的查询很困难，因此
	 * 目前不考虑。
	 */
	if (fc_jointype != JOIN_INNER && fc_jointype != JOIN_LEFT &&
		fc_jointype != JOIN_RIGHT && fc_jointype != JOIN_FULL)
		return false;

	/*
	 * 如果任一连接关系被标记为不安全以进行下推，则
	 * 连接不能被下推。
	 */
	fc_fpinfo = (PgFdwRelationInfo *) fc_joinrel->fdw_private;
	fc_fpinfo_o = (PgFdwRelationInfo *) fc_outerrel->fdw_private;
	fc_fpinfo_i = (PgFdwRelationInfo *) fc_innerrel->fdw_private;
	if (!fc_fpinfo_o || !fc_fpinfo_o->pushdown_safe ||
		!fc_fpinfo_i || !fc_fpinfo_i->pushdown_safe)
		return false;

	/*
	 * 如果连接关系具有本地条件，则在连接关系之前
	 * 需要应用这些条件。因此，连接不能被下推。
	 */
	if (fc_fpinfo_o->local_conds || fc_fpinfo_i->local_conds)
		return false;

	/*
	 * 合并 FDW 选项。我们可能会希望在判断
	 * 外部连接可以的情况下进行此操作。但是我们必须在此之前
	 * 执行此操作，以便了解哪些条件可以在外部服务器上
	 * 进行评估，这可能取决于可传送扩展。
	 */
	fc_fpinfo->server = fc_fpinfo_o->server;
	fc_merge_fdw_options(fc_fpinfo, fc_fpinfo_o, fc_fpinfo_i);

	/*
	 * 将限制列表拆分为连接条件和推送下去的（其他）条件。
	 *
	 * 属于外连接的连接条件必须都是可传输的，否则我们
	 * 无法远程执行连接。将这样的条件添加到'joinclauses'中。
	 *
	 * 如果其他条件是可传输的，则添加到fpinfo->remote_conds，否则添加到
	 * fpinfo->local_conds。在内连接中，条件可以在本地或远程执行；推送下去的条件
	 * 在外连接中也是如此。
	 *
	 * 注意，我们可能在已经写入
	 * fpinfo->remote_conds和fpinfo->local_conds之后返回失败。这是可以的，因为如果我们认为连接不可传输，就不会再次检查这些列表。
	 */
	fc_joinclauses = NIL;
	foreach(fc_lc, fc_extra->restrictlist)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_lc);
		bool		fc_is_remote_clause = is_foreign_expr(fc_root, fc_joinrel,
													   fc_rinfo->clause);

		if (IS_OUTER_JOIN(fc_jointype) &&
			!RINFO_IS_PUSHED_DOWN(fc_rinfo, fc_joinrel->relids))
		{
			if (!fc_is_remote_clause)
				return false;
			fc_joinclauses = lappend(fc_joinclauses, fc_rinfo);
		}
		else
		{
			if (fc_is_remote_clause)
				fc_fpinfo->remote_conds = lappend(fc_fpinfo->remote_conds, fc_rinfo);
			else
				fc_fpinfo->local_conds = lappend(fc_fpinfo->local_conds, fc_rinfo);
		}
	}

	/*
	 * deparseExplicitTargetList() 不够智能，无法处理除了 Var 以外的任何内容。
	 * 特别是，如果有某个 PlaceHolderVar 需要在这个连接树中进行评估
	 * （因为有对可能由于外连接而变为 NULL 的数量的上部引用），那么我们无法尝试
	 * 将连接下推，因为在到达 deparseExplicitTargetList() 时会失败。
	 * 然而，需要在这个连接树的*顶部*进行评估的 PlaceHolderVar 是可以的，因为
	 * 在从远程端获取结果后，我们可以在本地进行评估。
	 */
	foreach(fc_lc, fc_root->placeholder_list)
	{
		PlaceHolderInfo *fc_phinfo = lfirst(fc_lc);
		Relids		fc_relids;

		/* PlaceHolderInfo 指的是父 relid，而不是子 relid。 */
		fc_relids = IS_OTHER_REL(fc_joinrel) ?
			fc_joinrel->top_parent_relids : fc_joinrel->relids;

		if (bms_is_subset(fc_phinfo->ph_eval_at, fc_relids) &&
			bms_nonempty_difference(fc_relids, fc_phinfo->ph_eval_at))
			return false;
	}

	/* 保存连接条件，以便后续使用。 */
	fc_fpinfo->joinclauses = fc_joinclauses;

	fc_fpinfo->outerrel = fc_outerrel;
	fc_fpinfo->innerrel = fc_innerrel;
	fc_fpinfo->jointype = fc_jointype;

	/*
	 * 默认情况下，输入关系不需要被解析为子查询，但可能有一些由输入
	 * 关系覆盖的关系需要被解析为子查询，因此保存这些关系的
	 * relid 以便后续由解析器使用。
	 */
	fc_fpinfo->make_outerrel_subquery = false;
	fc_fpinfo->make_innerrel_subquery = false;
	Assert(bms_is_subset(fc_fpinfo_o->lower_subquery_rels, fc_outerrel->relids));
	Assert(bms_is_subset(fc_fpinfo_i->lower_subquery_rels, fc_innerrel->relids));
	fc_fpinfo->lower_subquery_rels = bms_union(fc_fpinfo_o->lower_subquery_rels,
											fc_fpinfo_i->lower_subquery_rels);

	/*
	 * 尽可能将其他远程条件从连接关系中提取到该关系的连接条件或其他远程条件（remote_conds）中。
	 * 这避免了在每个连接步骤中构建子查询。
	 *
	 * 对于内连接，来自两个关系的条件都会被添加到其他远程条件中。
	 * 对于左外连接和右外连接，来自外侧的条件会添加到 remote_conds，
	 * 因为这些条件可以在连接被评估后进行评估。来自内侧的条件被添加到
	 * joinclauses，因为它们需要在构建连接时进行评估。
	 *
	 * 对于全外连接，任何关系的其他条件都不能添加到 joinclauses 或 remote_conds，
	 * 因为每个关系在另一个关系中充当外部关系。
	 *
	 * 连接方不能有本地条件，因此无需测试被提升条件的可传输性。
	 */
	switch (fc_jointype)
	{
		case JOIN_INNER:
			fc_fpinfo->remote_conds = list_concat(fc_fpinfo->remote_conds,
											   fc_fpinfo_i->remote_conds);
			fc_fpinfo->remote_conds = list_concat(fc_fpinfo->remote_conds,
											   fc_fpinfo_o->remote_conds);
			break;

		case JOIN_LEFT:
			fc_fpinfo->joinclauses = list_concat(fc_fpinfo->joinclauses,
											  fc_fpinfo_i->remote_conds);
			fc_fpinfo->remote_conds = list_concat(fc_fpinfo->remote_conds,
											   fc_fpinfo_o->remote_conds);
			break;

		case JOIN_RIGHT:
			fc_fpinfo->joinclauses = list_concat(fc_fpinfo->joinclauses,
											  fc_fpinfo_o->remote_conds);
			fc_fpinfo->remote_conds = list_concat(fc_fpinfo->remote_conds,
											   fc_fpinfo_i->remote_conds);
			break;

		case JOIN_FULL:

			/*
			 * 在这种情况下，如果任何输入关系有条件，我们
			 * 需要将该关系解析为子查询，以便在连接之前可以
			 * 评估条件。记住它在这个关系的fpinfo中，以便
			 * 解析器可以采取适当的行动。同时，保存被该
			 * 关系覆盖的基本关系的relids，以便解析器后续使用。
			 */
			if (fc_fpinfo_o->remote_conds)
			{
				fc_fpinfo->make_outerrel_subquery = true;
				fc_fpinfo->lower_subquery_rels =
					bms_add_members(fc_fpinfo->lower_subquery_rels,
									fc_outerrel->relids);
			}
			if (fc_fpinfo_i->remote_conds)
			{
				fc_fpinfo->make_innerrel_subquery = true;
				fc_fpinfo->lower_subquery_rels =
					bms_add_members(fc_fpinfo->lower_subquery_rels,
									fc_innerrel->relids);
			}
			break;

		default:
			/* 这不应该发生，我们刚刚在上面检查过这点 */
			elog(ERROR, "unsupported join type %d", fc_jointype);
	}

	/*
	 * 对于内连接，所有限制都可以一样对待。将
	 * 下推条件视为连接条件可以使顶层的全外连接
	 * 被解析而不需要子查询。
	 */
	if (fc_jointype == JOIN_INNER)
	{
		Assert(!fc_fpinfo->joinclauses);
		fc_fpinfo->joinclauses = fc_fpinfo->remote_conds;
		fc_fpinfo->remote_conds = NIL;
	}

	/* 标记此连接可以安全下推 */
	fc_fpinfo->pushdown_safe = true;

	/* 获取用户映射 */
	if (fc_fpinfo->use_remote_estimate)
	{
		if (fc_fpinfo_o->use_remote_estimate)
			fc_fpinfo->user = fc_fpinfo_o->user;
		else
			fc_fpinfo->user = fc_fpinfo_i->user;
	}
	else
		fc_fpinfo->user = NULL;

	/*
	 * 将检索的行数和缓存的关系成本设置为某个负值，以便于在对 estimate_path_cost_size 的一次调用（通常是第一次）期间检测它们何时被设置为某些合理的值。
	 */
	fc_fpinfo->retrieved_rows = -1;
	fc_fpinfo->rel_startup_cost = -1;
	fc_fpinfo->rel_total_cost = -1;

	/*
	 * 设置描述此连接关系的字符串，以便在对应的
	 * ForeignScan的EXPLAIN输出中使用。请注意，我们
	 * 添加到基本关系名称上的装饰不能包含任何数字，
	 * 否则会混淆postgresExplainForeignScan。
	 */
	fc_fpinfo->relation_name = psprintf("(%s) %s JOIN (%s)",
									 fc_fpinfo_o->relation_name,
									 get_jointype_name(fc_fpinfo->jointype),
									 fc_fpinfo_i->relation_name);

	/*
	 * 设置关系索引。这被定义为此joinrel在join_rel_list
	 * 列表中的位置加上rtable列表的长度。请注意，由于
	 * 此joinrel在我们被调用时是在join_rel_list列表的
	 * 末尾，因此我们可以通过list_length获取位置。
	 */
	Assert(fc_fpinfo->relation_index == 0);	/* 还不应该设置 */
	fc_fpinfo->relation_index =
		list_length(fc_root->parse->rtable) + list_length(fc_root->join_rel_list);

	return true;
}

static void fc_add_paths_with_pathkeys_for_rel(PlannerInfo *fc_root, RelOptInfo *fc_rel,
								Path *fc_epq_path)
{
	List	   *fc_useful_pathkeys_list = NIL; /* 所有pathkeys的列表 */
	ListCell   *fc_lc;

	fc_useful_pathkeys_list = fc_get_useful_pathkeys_for_relation(fc_root, fc_rel);

	/*
	 * 在创建排序路径之前，安排传入的EPQ路径（如果
	 * 有）返回父ForeignScan节点所需的列，以便它们
	 * 可以在必要时通过下面注入的Sort节点向上传播。
	 */
	if (fc_epq_path != NULL && fc_useful_pathkeys_list != NIL)
	{
		PgFdwRelationInfo *fc_fpinfo = (PgFdwRelationInfo *) fc_rel->fdw_private;
		PathTarget *fc_target = copy_pathtarget(fc_epq_path->pathtarget);

		/* 在tlist中包含评估PHVs所需的列。 */
		add_new_columns_to_pathtarget(fc_target,
									  pull_var_clause((Node *) fc_target->exprs,
													  PVC_RECURSE_PLACEHOLDERS));

		/* 在tlist中包含评估本地条件所需的列。 */
		foreach(fc_lc, fc_fpinfo->local_conds)
		{
			RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_lc);

			add_new_columns_to_pathtarget(fc_target,
										  pull_var_clause((Node *) fc_rinfo->clause,
														  PVC_RECURSE_PLACEHOLDERS));
		}

		/*
		 * 如果我们添加了任何新列，调整EPQ路径的tlist。
		 *
		 * 注意：使用此路径创建的计划将仅用于执行
		 * EPQ检查，其中计划成本和宽度估计的准确性
		 * 并不重要，因此我们在这里不对新的pathtarget
		 * 调用set_pathtarget_cost_width()。另见
		 * postgresGetForeignPlan()。
		 */
		if (list_length(fc_target->exprs) > list_length(fc_epq_path->pathtarget->exprs))
		{
			/* EPQ路径是一个连接路径，因此它是支持投影的。 */
			Assert(is_projection_capable_path(fc_epq_path));

			/*
			 * 在这里使用create_projection_path()，以避免在
			 * 原地修改它。
			 */
			fc_epq_path = (Path *) create_projection_path(fc_root,
													   fc_rel,
													   fc_epq_path,
													   fc_target);
		}
	}

	/* 为我们在上面找到的每组pathkeys创建一个路径。 */
	foreach(fc_lc, fc_useful_pathkeys_list)
	{
		double		fc_rows;
		int			fc_width;
		Cost		fc_startup_cost;
		Cost		fc_total_cost;
		List	   *fc_useful_pathkeys = lfirst(fc_lc);
		Path	   *fc_sorted_epq_path;

		fc_estimate_path_cost_size(fc_root, fc_rel, NIL, fc_useful_pathkeys, NULL,
								&fc_rows, &fc_width, &fc_startup_cost, &fc_total_cost);

		/*
		 * EPQ路径必须至少与路径本身同样排序良好，
		 * 以防它被用作合并连接的输入。
		 */
		fc_sorted_epq_path = fc_epq_path;
		if (fc_sorted_epq_path != NULL &&
			!pathkeys_contained_in(fc_useful_pathkeys,
								   fc_sorted_epq_path->pathkeys))
			fc_sorted_epq_path = (Path *)
				create_sort_path(fc_root,
								 fc_rel,
								 fc_sorted_epq_path,
								 fc_useful_pathkeys,
								 -1.0);

		if (IS_SIMPLE_REL(fc_rel))
			add_path(fc_rel, (Path *)
					 create_foreignscan_path(fc_root, fc_rel,
											 NULL,
											 fc_rows,
											 fc_startup_cost,
											 fc_total_cost,
											 fc_useful_pathkeys,
											 fc_rel->lateral_relids,
											 fc_sorted_epq_path,
											 NIL));
		else
			add_path(fc_rel, (Path *)
					 create_foreign_join_path(fc_root, fc_rel,
											  NULL,
											  fc_rows,
											  fc_startup_cost,
											  fc_total_cost,
											  fc_useful_pathkeys,
											  fc_rel->lateral_relids,
											  fc_sorted_epq_path,
											  NIL));
	}
}

/*
 * 从外部服务器解析选项并将其应用于fpinfo。
 *
 * 新选项可能还需要调整merge_fdw_options()。
 */
static void fc_apply_server_options(PgFdwRelationInfo *fc_fpinfo)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_fpinfo->server->options)
	{
		DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

		if (strcmp(fc_def->defname, "use_remote_estimate") == 0)
			fc_fpinfo->use_remote_estimate = defGetBoolean(fc_def);
		else if (strcmp(fc_def->defname, "fdw_startup_cost") == 0)
			(void) parse_real(defGetString(fc_def), &fc_fpinfo->fdw_startup_cost, 0,
							  NULL);
		else if (strcmp(fc_def->defname, "fdw_tuple_cost") == 0)
			(void) parse_real(defGetString(fc_def), &fc_fpinfo->fdw_tuple_cost, 0,
							  NULL);
		else if (strcmp(fc_def->defname, "extensions") == 0)
			fc_fpinfo->shippable_extensions =
				ExtractExtensionList(defGetString(fc_def), false);
		else if (strcmp(fc_def->defname, "fetch_size") == 0)
			(void) parse_int(defGetString(fc_def), &fc_fpinfo->fetch_size, 0, NULL);
		else if (strcmp(fc_def->defname, "async_capable") == 0)
			fc_fpinfo->async_capable = defGetBoolean(fc_def);
	}
}

/*
 * 从外部表解析选项并将其应用于 fpinfo。
 *
 * 新选项可能还需要调整 merge_fdw_options()。
 */
static void fc_apply_table_options(PgFdwRelationInfo *fc_fpinfo)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_fpinfo->table->options)
	{
		DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

		if (strcmp(fc_def->defname, "use_remote_estimate") == 0)
			fc_fpinfo->use_remote_estimate = defGetBoolean(fc_def);
		else if (strcmp(fc_def->defname, "fetch_size") == 0)
			(void) parse_int(defGetString(fc_def), &fc_fpinfo->fetch_size, 0, NULL);
		else if (strcmp(fc_def->defname, "async_capable") == 0)
			fc_fpinfo->async_capable = defGetBoolean(fc_def);
	}
}

/*
 * 将输入关系的合并 FDW 选项合并到用于连接或上层关系的新选项集中。
 *
 * 对于连接关系，关于内部和外部关系的 FDW 特定信息通过 fpinfo_i 和 fpinfo_o 提供。对于上层关系，fpinfo_o 提供输入关系的信息；fpinfo_i 预计为 NULL。
 */
static void fc_merge_fdw_options(PgFdwRelationInfo *fc_fpinfo,
				  const PgFdwRelationInfo *fc_fpinfo_o,
				  const PgFdwRelationInfo *fc_fpinfo_i)
{
	/* 我们必须始终有 fpinfo_o。 */
	Assert(fc_fpinfo_o);

	/* fpinfo_i 可以为 NULL，但如果存在，服务器必须匹配。 */
	Assert(!fc_fpinfo_i ||
		   fc_fpinfo_i->server->serverid == fc_fpinfo_o->server->serverid);

	/*
	 * 复制服务器特定的 FDW 选项。（对于连接，两个关系来自同一服务器，因此服务器选项对于两个关系的值应该相同。）
	 */
	fc_fpinfo->fdw_startup_cost = fc_fpinfo_o->fdw_startup_cost;
	fc_fpinfo->fdw_tuple_cost = fc_fpinfo_o->fdw_tuple_cost;
	fc_fpinfo->shippable_extensions = fc_fpinfo_o->shippable_extensions;
	fc_fpinfo->use_remote_estimate = fc_fpinfo_o->use_remote_estimate;
	fc_fpinfo->fetch_size = fc_fpinfo_o->fetch_size;
	fc_fpinfo->async_capable = fc_fpinfo_o->async_capable;

	/* 合并来自连接任一方的表级选项。 */
	if (fc_fpinfo_i)
	{
		/*
		 * 如果连接任一侧的任何表使用远程估算，我们将更倾向于使用远程估算进行此连接。这很可能会被优先选择，因为他们已经愿意为获取远程 EXPLAIN 支付一次往返的代价。无论如何，尚不完全清楚我们可能如何处理这个问题。
		 */
		fc_fpinfo->use_remote_estimate = fc_fpinfo_o->use_remote_estimate ||
			fc_fpinfo_i->use_remote_estimate;

		/*
		 * 将获取大小设置为连接两侧的最大值，因为我们期望连接返回的行与关系的大小成比例。
		 */
		fc_fpinfo->fetch_size = Max(fc_fpinfo_o->fetch_size, fc_fpinfo_i->fetch_size);

		/*
		 * 如果连接任一侧的任何表被认为是异步可处理的，我们将更倾向于考虑此连接为异步可处理的。这是合理的，因为在这种情况下，外部服务器将拥有自己的资源以异步扫描该表，并且连接也可以使用这些资源异步计算。
		 */
		fc_fpinfo->async_capable = fc_fpinfo_o->async_capable ||
			fc_fpinfo_i->async_capable;
	}
}

/*
 * postgresGetForeignJoinPaths
 *		如果连接是安全的向下推送，则向 joinrel 添加可能的 ForeignPath。
 */
static void fc_postgresGetForeignJoinPaths(PlannerInfo *fc_root,
							RelOptInfo *fc_joinrel,
							RelOptInfo *fc_outerrel,
							RelOptInfo *fc_innerrel,
							JoinType fc_jointype,
							JoinPathExtraData *fc_extra)
{
	PgFdwRelationInfo *fc_fpinfo;
	ForeignPath *fc_joinpath;
	double		fc_rows;
	int			fc_width;
	Cost		fc_startup_cost;
	Cost		fc_total_cost;
	Path	   *fc_epq_path;		/* 创建路径以便在 EvalPlanQual 被触发时执行计划。 */

	/*
	 * 如果这个连接组合已经被考虑，则跳过。
	 */
	if (fc_joinrel->fdw_private)
		return;

	/*
	 * 此代码不适用于具有侧面引用的连接，因为这些必须具有参数化路径，而我们尚未生成这些路径。
	 */
	if (!bms_is_empty(fc_joinrel->lateral_relids))
		return;

	/*
	 * 创建未完成的 PgFdwRelationInfo 条目，用于指示连接关系已经被考虑，因此我们不会浪费时间判断连接下推的安全性并再次添加相同的路径（如果发现安全）。一旦我们知道这个连接可以下推，我们将填写该条目。
	 */
	fc_fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
	fc_fpinfo->pushdown_safe = false;
	fc_joinrel->fdw_private = fc_fpinfo;
	/* attrs_used 仅用于基础关系。 */
	fc_fpinfo->attrs_used = NULL;

	/*
	 * 如果有可能执行 EvalPlanQual，我们需要能够使用基关系的扫描来重建行。
	 * GetExistingLocalJoinPath 将在 joinrel 的路径列表中找到适合此目的的路径（如果存在）。
	 * 我们必须小心在添加任何 ForeignPath 之前调用它，因为 ForeignPath 可能会主导唯一可用的本地路径。
	 * 我们还在调用 foreign_join_ok() 之前这样做，因为该函数会更新 fpinfo 并将其标记为可下推，如果发现连接是可下推的。
	 */
	if (fc_root->parse->commandType == CMD_DELETE ||
		fc_root->parse->commandType == CMD_UPDATE ||
		fc_root->rowMarks)
	{
		fc_epq_path = GetExistingLocalJoinPath(fc_joinrel);
		if (!fc_epq_path)
		{
			elog(DEBUG3, "could not push down foreign join because a local path suitable for EPQ checks was not found");
			return;
		}
	}
	else
		fc_epq_path = NULL;

	if (!fc_foreign_join_ok(fc_root, fc_joinrel, fc_jointype, fc_outerrel, fc_innerrel, fc_extra))
	{
		/* 如果我们复制了一个，EPQ 需要释放路径；我们现在不需要它 */
		if (fc_epq_path)
			pfree(fc_epq_path);
		return;
	}

	/*
	 * 计算 local_conds 的选择性和成本，因此我们不必为每个路径重新计算。
	 * 对于这些条件，我们最好的做法是基于本地统计信息估计选择性。
	 * 本地条件在远程端计算连接后应用，类似于 WHERE 子句中的 qual，因此将 jointype 传递为 JOIN_INNER。
	 */
	fc_fpinfo->local_conds_sel = clauselist_selectivity(fc_root,
													 fc_fpinfo->local_conds,
													 0,
													 JOIN_INNER,
													 NULL);
	cost_qual_eval(&fc_fpinfo->local_conds_cost, fc_fpinfo->local_conds, fc_root);

	/*
	 * 如果我们要在本地估计成本，请在我们有特殊连接信息时这里估计连接子句的选择性。
	 */
	if (!fc_fpinfo->use_remote_estimate)
		fc_fpinfo->joinclause_sel = clauselist_selectivity(fc_root, fc_fpinfo->joinclauses,
														0, fc_fpinfo->jointype,
														fc_extra->sjinfo);

	/* 为裸连接关系估计成本 */
	fc_estimate_path_cost_size(fc_root, fc_joinrel, NIL, NIL, NULL,
							&fc_rows, &fc_width, &fc_startup_cost, &fc_total_cost);
	/* 现在在 joinrel 中更新此信息 */
	fc_joinrel->rows = fc_rows;
	fc_joinrel->reltarget->width = fc_width;
	fc_fpinfo->rows = fc_rows;
	fc_fpinfo->width = fc_width;
	fc_fpinfo->startup_cost = fc_startup_cost;
	fc_fpinfo->total_cost = fc_total_cost;

	/*
	 * 创建一个新的连接路径并将其添加到表示外部表之间连接的 joinrel 中。
	 */
	fc_joinpath = create_foreign_join_path(fc_root,
										fc_joinrel,
										NULL,	/* 默认的 pathtarget */
										fc_rows,
										fc_startup_cost,
										fc_total_cost,
										NIL,	/* 无 pathkeys */
										fc_joinrel->lateral_relids,
										fc_epq_path,
										NIL);	/* 无 fdw_private */

	/* 通过 add_path() 将生成的路径添加到 joinrel。 */
	add_path(fc_joinrel, (Path *) fc_joinpath);

	/* 考虑连接关系的路径键 */
	fc_add_paths_with_pathkeys_for_rel(fc_root, fc_joinrel, fc_epq_path);

	/* XXX 考虑连接关系的参数化路径 */
}

/*
 * 评估聚合、分组和 HAVING 操作是否可以下推到外部服务器。
 * 作为副作用，将我们在此函数中获得的信息保存到输入关系的 PgFdwRelationInfo 中。
 */
static bool fc_foreign_grouping_ok(PlannerInfo *fc_root, RelOptInfo *fc_grouped_rel,
					Node *fc_havingQual)
{
	Query	   *fc_query = fc_root->parse;
	PgFdwRelationInfo *fc_fpinfo = (PgFdwRelationInfo *) fc_grouped_rel->fdw_private;
	PathTarget *fc_grouping_target = fc_grouped_rel->reltarget;
	PgFdwRelationInfo *fc_ofpinfo;
	ListCell   *fc_lc;
	int			fc_i;
	List	   *fc_tlist = NIL;

	/* 我们目前不支持下推分组集。 */
	if (fc_query->groupingSets)
		return false;

	/* 获取基础扫描关系的 fpinfo。 */
	fc_ofpinfo = (PgFdwRelationInfo *) fc_fpinfo->outerrel->fdw_private;

	/*
	 * 如果基础扫描关系有任何本地条件，则必须在执行聚合之前应用这些条件。
	 * 因此，聚合不能下推。
	 */
	if (fc_ofpinfo->local_conds)
		return false;

	/*
	 * 检查分组表达式以及我们需要计算的其他表达式，并检查它们是否可以安全地推送到外部服务器。所有的 GROUP BY 表达式将成为分组目标的一部分，因此不需要单独搜索它们。将分组表达式添加到将传递给外部服务器的目标列表中。
	 *
	 * 一个棘手的细节是，我们不能将任何仅仅是外部参数的表达式放入目标列表中（也就是说，deparse.c 将会得出必须发送到外部服务器的结论）。如果我们这样做，表达式还会出现在计划节点的 fdw_exprs 列表中，setrefs.c 将会感到困惑，并决定 fdw_exprs 项实际上是对 fdw_scan_tlist 项的引用，导致计划失效。有点奇怪的是，如果表达式包含这样的节点，只要它不是在最上层，那么是可以的；这样就不会有匹配的可能性。
	 */
	fc_i = 0;
	foreach(fc_lc, fc_grouping_target->exprs)
	{
		Expr	   *fc_expr = (Expr *) lfirst(fc_lc);
		Index		fc_sgref = get_pathtarget_sortgroupref(fc_grouping_target, fc_i);
		ListCell   *fc_l;

		/* 检查此表达式是否是 GROUP BY 子句的一部分 */
		if (fc_sgref && get_sortgroupref_clause_noerr(fc_sgref, fc_query->groupClause))
		{
			TargetEntry *fc_tle;

			/*
			 * 如果任何 GROUP BY 表达式不可推送，则我们无法将聚合推送到外部服务器。
			 */
			if (!is_foreign_expr(fc_root, fc_grouped_rel, fc_expr))
				return false;

			/*
			 * 如果它是外部参数，我们不能将其放入 tlist，因此我们必须失败。
			 */
			if (is_foreign_param(fc_root, fc_grouped_rel, fc_expr))
				return false;

			/*
			 * 可推送，因此添加到 tlist。我们需要为此表达式创建一个 TLE 并应用 sortgroupref。我们不能在这里使用 add_to_flat_tlist()，因为那会避免在 tlist 中生成重复条目。如果有 distinct sortgrouprefs 的重复条目，我们必须在输出 tlist 中复制该情况。
			 */
			fc_tle = makeTargetEntry(fc_expr, list_length(fc_tlist) + 1, NULL, false);
			fc_tle->ressortgroupref = fc_sgref;
			fc_tlist = lappend(fc_tlist, fc_tle);
		}
		else
		{
			/*
			 * 我们需要计算的非分组表达式。我们可以将其原样发送到外部服务器吗？
			 */
			if (is_foreign_expr(fc_root, fc_grouped_rel, fc_expr) &&
				!is_foreign_param(fc_root, fc_grouped_rel, fc_expr))
			{
				/* 可以，因此按原样添加到 tlist；可以抑制重复项 */
				fc_tlist = add_to_flat_tlist(fc_tlist, list_make1(fc_expr));
			}
			else
			{
				/* 整体不可推送；提取其变量和聚合 */
				List	   *fc_aggvars;

				fc_aggvars = pull_var_clause((Node *) fc_expr,
										  PVC_INCLUDE_AGGREGATES);

				/*
				 * 如果任何聚合表达式不可推送，则我们无法将聚合推送到外部服务器。（我们不需要检查 is_foreign_param，因为对于任何这样的表达式，它肯定不会返回 true。）
				 */
				if (!is_foreign_expr(fc_root, fc_grouped_rel, (Expr *) fc_aggvars))
					return false;

				/*
				 * 将聚合（如果有的话）添加到目标列表中。在聚合外的普通变量可以忽略，因为它们应该与某个 GROUP BY 列相同或是某个 GROUP BY 表达式的一部分。在这两种情况下，它们已经是目标列表的一部分，因此无需再次添加。实际上，当普通变量与 GROUP BY 列不匹配时将其包含在 tlist 中会导致外部服务器抱怨所发送的查询无效。
				 */
				foreach(fc_l, fc_aggvars)
				{
					Expr	   *fc_expr = (Expr *) lfirst(fc_l);

					if (IsA(fc_expr, Aggref))
						fc_tlist = add_to_flat_tlist(fc_tlist, list_make1(fc_expr));
				}
			}
		}

		fc_i++;
	}

	/*
	 * 将可推送和不可推送的 HAVING 子句进行分类，并将它们保存在分组关系的 fpinfo 的 remote_conds 和 local_conds 中。
	 */
	if (fc_havingQual)
	{
		ListCell   *fc_lc;

		foreach(fc_lc, (List *) fc_havingQual)
		{
			Expr	   *fc_expr = (Expr *) lfirst(fc_lc);
			RestrictInfo *fc_rinfo;

			/*
			 * 目前，核心代码并没有将 havingQuals 包裹在 RestrictInfos 中，因此我们必须自己处理。
			 */
			Assert(!IsA(fc_expr, RestrictInfo));
			fc_rinfo = make_restrictinfo(fc_root,
									  fc_expr,
									  true,
									  false,
									  false,
									  fc_root->qual_security_level,
									  fc_grouped_rel->relids,
									  NULL,
									  NULL);
			if (is_foreign_expr(fc_root, fc_grouped_rel, fc_expr))
				fc_fpinfo->remote_conds = lappend(fc_fpinfo->remote_conds, fc_rinfo);
			else
				fc_fpinfo->local_conds = lappend(fc_fpinfo->local_conds, fc_rinfo);
		}
	}

	/*
	 * 如果存在任何本地条件，从中提取Vars和聚合，并检查它们是否安全进行下推。
	 */
	if (fc_fpinfo->local_conds)
	{
		List	   *fc_aggvars = NIL;
		ListCell   *fc_lc;

		foreach(fc_lc, fc_fpinfo->local_conds)
		{
			RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_lc);

			fc_aggvars = list_concat(fc_aggvars,
								  pull_var_clause((Node *) fc_rinfo->clause,
												  PVC_INCLUDE_AGGREGATES));
		}

		foreach(fc_lc, fc_aggvars)
		{
			Expr	   *fc_expr = (Expr *) lfirst(fc_lc);

			/*
			 * 如果本地条件中的聚合不安全进行下推，则我们无法下推查询。Vars
			 * 已经是GROUP BY子句的一部分，之前已经检查过，因此这里不需要再次访问它们。
			 * 同样，我们也不需要检查外部聚合的is_foreign_param。
			 */
			if (IsA(fc_expr, Aggref))
			{
				if (!is_foreign_expr(fc_root, fc_grouped_rel, fc_expr))
					return false;

				fc_tlist = add_to_flat_tlist(fc_tlist, list_make1(fc_expr));
			}
		}
	}

	/* 存储生成的目标列表 */
	fc_fpinfo->grouped_tlist = fc_tlist;

	/* 安全进行下推 */
	fc_fpinfo->pushdown_safe = true;

	/*
	 * 将检索的行数和缓存的关系成本设置为某个负值，以便于在对 estimate_path_cost_size 的一次调用（通常是第一次）期间检测它们何时被设置为某些合理的值。
	 */
	fc_fpinfo->retrieved_rows = -1;
	fc_fpinfo->rel_startup_cost = -1;
	fc_fpinfo->rel_total_cost = -1;

	/*
	 * 设置描述此分组关系的字符串，用于对应ForeignScan的EXPLAIN输出。
	 * 请注意，我们添加到基础关系名称的装饰不能包含任何数字，否则会让
	 * postgresExplainForeignScan感到困惑。
	 */
	fc_fpinfo->relation_name = psprintf("Aggregate on (%s)",
									 fc_ofpinfo->relation_name);

	return true;
}

/*
 * postgresGetForeignUpperPaths
 *		如果相关操作安全进行下推，则添加用于后连接操作（如聚合、分组等）的路径。
 */
static void fc_postgresGetForeignUpperPaths(PlannerInfo *fc_root, UpperRelationKind fc_stage,
							 RelOptInfo *fc_input_rel, RelOptInfo *fc_output_rel,
							 void *fc_extra)
{
	PgFdwRelationInfo *fc_fpinfo;

	/*
	 * 如果输入关系不安全进行下推，则直接返回，因为我们无法在外部服务器上执行
	 * 任何后连接操作。
	 */
	if (!fc_input_rel->fdw_private ||
		!((PgFdwRelationInfo *) fc_input_rel->fdw_private)->pushdown_safe)
		return;

	/* 忽略我们不支持的阶段；跳过任何重复调用。 */
	if ((fc_stage != UPPERREL_GROUP_AGG &&
		 fc_stage != UPPERREL_ORDERED &&
		 fc_stage != UPPERREL_FINAL) ||
		fc_output_rel->fdw_private)
		return;

	fc_fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
	fc_fpinfo->pushdown_safe = false;
	fc_fpinfo->stage = fc_stage;
	fc_output_rel->fdw_private = fc_fpinfo;

	switch (fc_stage)
	{
		case UPPERREL_GROUP_AGG:
			fc_add_foreign_grouping_paths(fc_root, fc_input_rel, fc_output_rel,
									   (GroupPathExtraData *) fc_extra);
			break;
		case UPPERREL_ORDERED:
			fc_add_foreign_ordered_paths(fc_root, fc_input_rel, fc_output_rel);
			break;
		case UPPERREL_FINAL:
			fc_add_foreign_final_paths(fc_root, fc_input_rel, fc_output_rel,
									(FinalPathExtraData *) fc_extra);
			break;
		default:
			elog(ERROR, "unexpected upper relation: %d", (int) fc_stage);
			break;
	}
}

/*
 * add_foreign_grouping_paths
 *		为分组和/或聚合添加外部路径。
 *
 * 给定的input_rel代表底层扫描。路径将添加到给定的grouped_rel。
 */
static void fc_add_foreign_grouping_paths(PlannerInfo *fc_root, RelOptInfo *fc_input_rel,
						   RelOptInfo *fc_grouped_rel,
						   GroupPathExtraData *fc_extra)
{
	Query	   *fc_parse = fc_root->parse;
	PgFdwRelationInfo *fc_ifpinfo = fc_input_rel->fdw_private;
	PgFdwRelationInfo *fc_fpinfo = fc_grouped_rel->fdw_private;
	ForeignPath *fc_grouppath;
	double		fc_rows;
	int			fc_width;
	Cost		fc_startup_cost;
	Cost		fc_total_cost;

	/* 如果不需要分组或聚合，则无需执行任何操作。 */
	if (!fc_parse->groupClause && !fc_parse->groupingSets && !fc_parse->hasAggs &&
		!fc_root->hasHavingQual)
		return;

	Assert(fc_extra->patype == PARTITIONWISE_AGGREGATE_NONE ||
		   fc_extra->patype == PARTITIONWISE_AGGREGATE_FULL);

	/* 将input_rel作为outerrel保存在fpinfo中 */
	fc_fpinfo->outerrel = fc_input_rel;

	/*
	 * 从输入关系的fpinfo中复制外部表、外部服务器、用户映射、FDW选项等详细信息。
	 */
	fc_fpinfo->table = fc_ifpinfo->table;
	fc_fpinfo->server = fc_ifpinfo->server;
	fc_fpinfo->user = fc_ifpinfo->user;
	fc_merge_fdw_options(fc_fpinfo, fc_ifpinfo, NULL);

	/*
	 * 评估是否安全进行聚合和分组下推。
	 *
	 * 使用来自extra的HAVING条件。如果是子分区，将包含翻译后的Vars。
	 */
	if (!fc_foreign_grouping_ok(fc_root, fc_grouped_rel, fc_extra->havingQual))
		return;

	/*
	 * 计算local_conds的选择性和成本，以便我们不必为每条路径重复进行此操作。
	 * （目前我们在这里只创建一条路径，但将来可能会构建更多路径，例如
	 * postgresGetForeignPaths和postgresGetForeignJoinPaths中的预排序路径。）
	 * 对于这些条件，我们能做的最好就是根据本地统计数据估算选择性。
	 */
	fc_fpinfo->local_conds_sel = clauselist_selectivity(fc_root,
													 fc_fpinfo->local_conds,
													 0,
													 JOIN_INNER,
													 NULL);

	cost_qual_eval(&fc_fpinfo->local_conds_cost, fc_fpinfo->local_conds, fc_root);

	/* 估算下推的成本 */
	fc_estimate_path_cost_size(fc_root, fc_grouped_rel, NIL, NIL, NULL,
							&fc_rows, &fc_width, &fc_startup_cost, &fc_total_cost);

	/* 现在在fpinfo中更新此信息 */
	fc_fpinfo->rows = fc_rows;
	fc_fpinfo->width = fc_width;
	fc_fpinfo->startup_cost = fc_startup_cost;
	fc_fpinfo->total_cost = fc_total_cost;

	/* 创建并将外部路径添加到分组关系中。 */
	fc_grouppath = create_foreign_upper_path(fc_root,
										  fc_grouped_rel,
										  fc_grouped_rel->reltarget,
										  fc_rows,
										  fc_startup_cost,
										  fc_total_cost,
										  NIL,	/* 无 pathkeys */
										  NULL,
										  NIL); /* 无 fdw_private */

	/* 通过add_path()将生成的路径添加到grouped_rel中。 */
	add_path(fc_grouped_rel, (Path *) fc_grouppath);
}

/*
 * add_foreign_ordered_paths
 *		添加外部路径以在远程执行最终排序。
 *
 * 给定的input_rel包含源数据路径。路径将添加到给定的ordered_rel。
 */
static void fc_add_foreign_ordered_paths(PlannerInfo *fc_root, RelOptInfo *fc_input_rel,
						  RelOptInfo *fc_ordered_rel)
{
	Query	   *fc_parse = fc_root->parse;
	PgFdwRelationInfo *fc_ifpinfo = fc_input_rel->fdw_private;
	PgFdwRelationInfo *fc_fpinfo = fc_ordered_rel->fdw_private;
	PgFdwPathExtraData *fc_fpextra;
	double		fc_rows;
	int			fc_width;
	Cost		fc_startup_cost;
	Cost		fc_total_cost;
	List	   *fc_fdw_private;
	ForeignPath *fc_ordered_path;
	ListCell   *fc_lc;

	/* 只有在查询包含 ORDER BY 时才会到达这里 */
	Assert(fc_parse->sortClause);

	/* 我们不支持目标列表中有任何 SRF 的情况 */
	if (fc_parse->hasTargetSRFs)
		return;

	/* 将 input_rel 作为 outerrel 保存到 fpinfo 中 */
	fc_fpinfo->outerrel = fc_input_rel;

	/*
	 * 从输入关系的fpinfo中复制外部表、外部服务器、用户映射、FDW选项等详细信息。
	 */
	fc_fpinfo->table = fc_ifpinfo->table;
	fc_fpinfo->server = fc_ifpinfo->server;
	fc_fpinfo->user = fc_ifpinfo->user;
	fc_merge_fdw_options(fc_fpinfo, fc_ifpinfo, NULL);

	/*
	 * 如果 input_rel 是基关系或连接关系，当创建该关系的预排序外部路径时
	 * 我们已经考虑将最终排序下推到远程服务器，因为在这种情况下
	 * query_pathkeys 设置为 root->sort_pathkeys（见 standard_qp_callback()）。
	 */
	if (fc_input_rel->reloptkind == RELOPT_BASEREL ||
		fc_input_rel->reloptkind == RELOPT_JOINREL)
	{
		Assert(fc_root->query_pathkeys == fc_root->sort_pathkeys);

		/* 如果 query_pathkeys 可以安全推送，则可以安全下推 */
		fc_fpinfo->pushdown_safe = fc_ifpinfo->qp_is_pushdown_safe;

		return;
	}

	/* input_rel 应该是分组关系 */
	Assert(fc_input_rel->reloptkind == RELOPT_UPPER_REL &&
		   fc_ifpinfo->stage == UPPERREL_GROUP_AGG);

	/*
	 * 我们尝试通过扩展底层分组关系的简单外部路径来创建如下路径，
	 * 以便远程执行最终排序，该路径存储在结果路径的 fdw_private 列表中。
	 */

	/* 评估是否可以安全地推送最终排序 */
	foreach(fc_lc, fc_root->sort_pathkeys)
	{
		PathKey    *fc_pathkey = (PathKey *) lfirst(fc_lc);
		EquivalenceClass *fc_pathkey_ec = fc_pathkey->pk_eclass;

		/*
		 * is_foreign_expr 也会检测易变表达式，但检查 ec_has_volatile 在这里节省了一些周期。
		 */
		if (fc_pathkey_ec->ec_has_volatile)
			return;

		/*
		 * 如果 pathkey 的操作族不是可下推的，则无法下推排序。
		 */
		if (!is_shippable(fc_pathkey->pk_opfamily, OperatorFamilyRelationId,
						  fc_fpinfo))
			return;

		/*
		 * EC 必须包含一个在 input_rel 的 reltarget 中计算的可下推的 EM，
		 * 否则我们无法下推排序。
		 */
		if (find_em_for_rel_target(fc_root,
								   fc_pathkey_ec,
								   fc_input_rel) == NULL)
			return;
	}

	/* 安全下推 */
	fc_fpinfo->pushdown_safe = true;

	/* 构造 PgFdwPathExtraData */
	fc_fpextra = (PgFdwPathExtraData *) palloc0(sizeof(PgFdwPathExtraData));
	fc_fpextra->target = fc_root->upper_targets[UPPERREL_ORDERED];
	fc_fpextra->has_final_sort = true;

	/* 估算远程执行最终排序的成本 */
	fc_estimate_path_cost_size(fc_root, fc_input_rel, NIL, fc_root->sort_pathkeys, fc_fpextra,
							&fc_rows, &fc_width, &fc_startup_cost, &fc_total_cost);

	/*
	 * 构建将被 postgresGetForeignPlan 使用的 fdw_private 列表。
	 * 列表中的项必须与枚举 FdwPathPrivateIndex 的顺序匹配。
	 */
	fc_fdw_private = list_make2(makeBoolean(true), makeBoolean(false));

	/* 创建外部排序路径 */
	fc_ordered_path = create_foreign_upper_path(fc_root,
											 fc_input_rel,
											 fc_root->upper_targets[UPPERREL_ORDERED],
											 fc_rows,
											 fc_startup_cost,
											 fc_total_cost,
											 fc_root->sort_pathkeys,
											 NULL,	/* 无额外计划 */
											 fc_fdw_private);

	/* 并将其添加到 ordered_rel */
	add_path(fc_ordered_rel, (Path *) fc_ordered_path);
}

/*
 * add_foreign_final_paths
 *		为远程执行最终处理添加外部路径。
 *
 * 给定 input_rel 包含源数据路径。路径被添加到给定的 final_rel 中。
 */
static void fc_add_foreign_final_paths(PlannerInfo *fc_root, RelOptInfo *fc_input_rel,
						RelOptInfo *fc_final_rel,
						FinalPathExtraData *fc_extra)
{
	Query	   *fc_parse = fc_root->parse;
	PgFdwRelationInfo *fc_ifpinfo = (PgFdwRelationInfo *) fc_input_rel->fdw_private;
	PgFdwRelationInfo *fc_fpinfo = (PgFdwRelationInfo *) fc_final_rel->fdw_private;
	bool		fc_has_final_sort = false;
	List	   *fc_pathkeys = NIL;
	PgFdwPathExtraData *fc_fpextra;
	bool		fc_save_use_remote_estimate = false;
	double		fc_rows;
	int			fc_width;
	Cost		fc_startup_cost;
	Cost		fc_total_cost;
	List	   *fc_fdw_private;
	ForeignPath *fc_final_path;

	/*
	 * 目前我们只支持 SELECT 命令
	 */
	if (fc_parse->commandType != CMD_SELECT)
		return;

	/*
	 * 如果没有 FOR UPDATE/SHARE 子句，并且不需要添加 LIMIT 节点，
	 * 则没有任何工作
	 */
	if (!fc_parse->rowMarks && !fc_extra->limit_needed)
		return;

	/* 我们不支持目标列表中有任何 SRF 的情况 */
	if (fc_parse->hasTargetSRFs)
		return;

	/* 将 input_rel 作为 outerrel 保存到 fpinfo 中 */
	fc_fpinfo->outerrel = fc_input_rel;

	/*
	 * 从输入关系的fpinfo中复制外部表、外部服务器、用户映射、FDW选项等详细信息。
	 */
	fc_fpinfo->table = fc_ifpinfo->table;
	fc_fpinfo->server = fc_ifpinfo->server;
	fc_fpinfo->user = fc_ifpinfo->user;
	fc_merge_fdw_options(fc_fpinfo, fc_ifpinfo, NULL);

	/*
	 * 如果不需要添加 LIMIT 节点，input_rel 的路径列表中可能存在实现查询全部行为的 ForeignPath。
	 * 注意：在到达这里之前，我们已经考虑了查询的 FOR UPDATE/SHARE（如果有）。
	 */
	if (!fc_extra->limit_needed)
	{
		ListCell   *fc_lc;

		Assert(fc_parse->rowMarks);

		/*
		 * 分组和聚合不支持 FOR UPDATE/SHARE，
		 * 因此 input_rel 应该是基关系、连接关系或有序关系；如果它是有序关系，
		 * 则其输入关系应为基关系或连接关系。
		 */
		Assert(fc_input_rel->reloptkind == RELOPT_BASEREL ||
			   fc_input_rel->reloptkind == RELOPT_JOINREL ||
			   (fc_input_rel->reloptkind == RELOPT_UPPER_REL &&
				fc_ifpinfo->stage == UPPERREL_ORDERED &&
				(fc_ifpinfo->outerrel->reloptkind == RELOPT_BASEREL ||
				 fc_ifpinfo->outerrel->reloptkind == RELOPT_JOINREL)));

		foreach(fc_lc, fc_input_rel->pathlist)
		{
			Path	   *fc_path = (Path *) lfirst(fc_lc);

			/*
			 * apply_scanjoin_target_to_paths() 使用 create_projection_path()
			 * 来调整每个输入路径（如果需要），而
			 * create_ordered_paths() 使用 apply_projection_to_path() 来完成
			 * 这项工作。因此，前者可能在 ForeignPath 上放置了一个 ProjectionPath；
			 * 逐层查看 ProjectionPath，确保其下方的路径是 ForeignPath。
			 */
			if (IsA(fc_path, ForeignPath) ||
				(IsA(fc_path, ProjectionPath) &&
				 IsA(((ProjectionPath *) fc_path)->subpath, ForeignPath)))
			{
				/*
				 * 创建外部最终路径；这会去除一个
				 * 不再需要的外部计划（如果有），使得
				 * EXPLAIN 输出看起来更简洁。
				 */
				fc_final_path = create_foreign_upper_path(fc_root,
													   fc_path->parent,
													   fc_path->pathtarget,
													   fc_path->rows,
													   fc_path->startup_cost,
													   fc_path->total_cost,
													   fc_path->pathkeys,
													   NULL,	/* 无额外计划 */
													   NULL);	/* 无 fdw_private */

				/* 并将其添加到 final_rel */
				add_path(fc_final_rel, (Path *) fc_final_path);

				/* 安全下推 */
				fc_fpinfo->pushdown_safe = true;

				return;
			}
		}

		/*
		 * 如果我们到达这里，意味着没有 ForeignPaths；
		 * 由于我们已经考虑过将查询的所有操作推送到
		 * 远程服务器，因此放弃这个查询。
		 */
		return;
	}

	Assert(fc_extra->limit_needed);

	/*
	 * 如果 input_rel 是有序关系，将 input_rel 替换为其
	 * 输入关系。
	 */
	if (fc_input_rel->reloptkind == RELOPT_UPPER_REL &&
		fc_ifpinfo->stage == UPPERREL_ORDERED)
	{
		fc_input_rel = fc_ifpinfo->outerrel;
		fc_ifpinfo = (PgFdwRelationInfo *) fc_input_rel->fdw_private;
		fc_has_final_sort = true;
		fc_pathkeys = fc_root->sort_pathkeys;
	}

	/* input_rel 应该是基本关系、连接关系或分组关系 */
	Assert(fc_input_rel->reloptkind == RELOPT_BASEREL ||
		   fc_input_rel->reloptkind == RELOPT_JOINREL ||
		   (fc_input_rel->reloptkind == RELOPT_UPPER_REL &&
			fc_ifpinfo->stage == UPPERREL_GROUP_AGG));

	/*
	 * 我们尝试通过扩展基础的简单外部路径来创建
	 * 一个路径，以便在远程执行最终的排序（如果有 final_sort）
	 * 和 LIMIT 限制，这将存储在结果路径的 fdw_private 列表中。
	 * （如果有 final_sort，我们会重新估算排序基础关系的成本。）
	 */

	/*
	 * 评估将 LIMIT 和 OFFSET 安全推送到远程
	 * 服务器的可能性。
	 */

	/*
	 * 如果基础关系有任何本地条件，则无法推送
	 * LIMIT/OFFSET。
	 */
	if (fc_ifpinfo->local_conds)
		return;

	/*
	 * 如果查询有 FETCH FIRST .. WITH TIES，1) 它必须也有 ORDER BY，
	 * 用于确定哪些额外行在结果集中平局，以及 2) 在我们到达这里之前，
	 * ORDER BY 必须已经确定为安全推送。因此，在这种情况下，
	 * 如果远程服务器是 v13 或更高版本，FETCH 子句是安全推送的
	 * （以及 ORDER BY），但如果不是，远程查询将完全失败，
	 * 因为缺乏对它的支持。由于我们当前没有办法进行
	 * 远程版本检查（不访问远程服务器），暂时禁用
	 * FETCH 子句的推送。
	 */
	if (fc_parse->limitOption == LIMIT_OPTION_WITH_TIES)
		return;

	/*
	 * 此外，如果 LIMIT/OFFSET 的表达式
	 * 不安全，则不能推送。
	 */
	if (!is_foreign_expr(fc_root, fc_input_rel, (Expr *) fc_parse->limitOffset) ||
		!is_foreign_expr(fc_root, fc_input_rel, (Expr *) fc_parse->limitCount))
		return;

	/* 安全下推 */
	fc_fpinfo->pushdown_safe = true;

	/* 构造 PgFdwPathExtraData */
	fc_fpextra = (PgFdwPathExtraData *) palloc0(sizeof(PgFdwPathExtraData));
	fc_fpextra->target = fc_root->upper_targets[UPPERREL_FINAL];
	fc_fpextra->has_final_sort = fc_has_final_sort;
	fc_fpextra->has_limit = fc_extra->limit_needed;
	fc_fpextra->limit_tuples = fc_extra->limit_tuples;
	fc_fpextra->count_est = fc_extra->count_est;
	fc_fpextra->offset_est = fc_extra->offset_est;

	/*
	 * 评估远程执行最终排序和 LIMIT
	 * 限制的成本。如果 has_final_sort 为假，
	 * 我们就不需要在使用 use_remote_estimate 时执行 EXPLAIN，
	 * 因为可以使用我们已经为基础关系拥有的成本
	 * 大致估算成本，就像 use_remote_estimate 为假时一样。
	 * 由于执行 EXPLAIN 的成本相当高，因此在这种情况下
	 * 强制将 use_remote_estimate 设置为假。
	 */
	if (!fc_fpextra->has_final_sort)
	{
		fc_save_use_remote_estimate = fc_ifpinfo->use_remote_estimate;
		fc_ifpinfo->use_remote_estimate = false;
	}
	fc_estimate_path_cost_size(fc_root, fc_input_rel, NIL, fc_pathkeys, fc_fpextra,
							&fc_rows, &fc_width, &fc_startup_cost, &fc_total_cost);
	if (!fc_fpextra->has_final_sort)
		fc_ifpinfo->use_remote_estimate = fc_save_use_remote_estimate;

	/*
	 * 构建将被 postgresGetForeignPlan 使用的 fdw_private 列表。
	 * 列表中的项必须与枚举 FdwPathPrivateIndex 的顺序匹配。
	 */
	fc_fdw_private = list_make2(makeBoolean(fc_has_final_sort),
							 makeBoolean(fc_extra->limit_needed));

	/*
	 * 创建外部最终路径；这将消除不再需要的外部
	 * 计划（如果有的话），这使得 EXPLAIN 输出看起来更整洁
	 */
	fc_final_path = create_foreign_upper_path(fc_root,
										   fc_input_rel,
										   fc_root->upper_targets[UPPERREL_FINAL],
										   fc_rows,
										   fc_startup_cost,
										   fc_total_cost,
										   fc_pathkeys,
										   NULL,	/* 无额外计划 */
										   fc_fdw_private);

	/* 并将其添加到 final_rel */
	add_path(fc_final_rel, (Path *) fc_final_path);
}

/*
 * postgresIsForeignPathAsyncCapable
 *		检查给定的 ForeignPath 节点是否支持异步。
 */
static bool fc_postgresIsForeignPathAsyncCapable(ForeignPath *fc_path)
{
	RelOptInfo *fc_rel = ((Path *) fc_path)->parent;
	PgFdwRelationInfo *fc_fpinfo = (PgFdwRelationInfo *) fc_rel->fdw_private;

	return fc_fpinfo->async_capable;
}

/*
 * postgresForeignAsyncRequest
 *		异步请求来自外部 PostgreSQL 表的下一个元组。
 */
static void fc_postgresForeignAsyncRequest(AsyncRequest *fc_areq)
{
	fc_produce_tuple_asynchronously(fc_areq, true);
}

/*
 * postgresForeignAsyncConfigureWait
 *		配置我们希望等待的文件描述符事件。
 */
static void fc_postgresForeignAsyncConfigureWait(AsyncRequest *fc_areq)
{
	ForeignScanState *fc_node = (ForeignScanState *) fc_areq->requestee;
	PgFdwScanState *fc_fsstate = (PgFdwScanState *) fc_node->fdw_state;
	AsyncRequest *fc_pendingAreq = fc_fsstate->conn_state->pendingAreq;
	AppendState *fc_requestor = (AppendState *) fc_areq->requestor;
	WaitEventSet *fc_set = fc_requestor->as_eventset;

	/* 除非 callback_pending，否则不应调用此函数 */
	Assert(fc_areq->callback_pending);

	/*
	 * 如果在我们到达这里之前，process_pending_request() 已在给定请求上被调用，
	 * 我们可能已经有一些元组；在这种情况下，
	 * 完成请求。
	 */
	if (fc_fsstate->next_tuple < fc_fsstate->num_tuples)
	{
		fc_complete_pending_request(fc_areq);
		if (fc_areq->request_complete)
			return;
		Assert(fc_areq->callback_pending);
	}

	/* 我们必须已经用完元组 */
	Assert(fc_fsstate->next_tuple >= fc_fsstate->num_tuples);

	/* 核心代码将注册后端进程死亡事件 */
	Assert(GetNumRegisteredWaitEvents(fc_set) >= 1);

	/* 如果尚未完成，则开始异步数据获取 */
	if (!fc_pendingAreq)
		fc_fetch_more_data_begin(fc_areq);
	else if (fc_pendingAreq->requestor != fc_areq->requestor)
	{
		/*
		 * 这是在进程请求是由另一个
		 * Append 发起的情况下。请注意，处理该 Append 发起的请求可能是无用的，
		 * 因为查询可能不再需要来自该 Append 的元组；因此如果可能的话，我们避免处理它以开始根据
		 * 给定请求进行获取。如果同一父级的任何子计划准备好接收新请求，则跳过给定
		 * 请求。同样，如果有任何配置的事件不是
		 * 后端进程死亡事件，则跳过它。否则，处理
		 * 跟进请求，然后开始一次获取以配置下面的事件，
		 * 因为否则我们可能最终没有配置的事件
		 * 除了后端进程死亡事件。
		 */
		if (!bms_is_empty(fc_requestor->as_needrequest))
			return;
		if (GetNumRegisteredWaitEvents(fc_set) > 1)
			return;
		process_pending_request(fc_pendingAreq);
		fc_fetch_more_data_begin(fc_areq);
	}
	else if (fc_pendingAreq->requestee != fc_areq->requestee)
	{
		/*
		 * 这是在进程请求由同一
		 * 父级发起但针对不同子级的情况下。因为我们仅为该子级发起的请求配置事件，
		 * 跳过给定请求。
		 */
		return;
	}
	else
		Assert(fc_pendingAreq == fc_areq);

	AddWaitEventToSet(fc_set, WL_SOCKET_READABLE, PQsocket(fc_fsstate->conn),
					  NULL, fc_areq);
}

/*
 * postgresForeignAsyncNotify
 *		从变为就绪的文件描述符获取更多元组，
 *		请求下一个元组。
 */
static void fc_postgresForeignAsyncNotify(AsyncRequest *fc_areq)
{
	ForeignScanState *fc_node = (ForeignScanState *) fc_areq->requestee;
	PgFdwScanState *fc_fsstate = (PgFdwScanState *) fc_node->fdw_state;

	/* 核心代码将初始化 callback_pending 标志 */
	Assert(!fc_areq->callback_pending);

	/*
	 * 如果在我们到达这里之前，process_pending_request() 已在给定请求上被调用，
	 * 我们可能已经有一些元组；在这种情况下，
	 * 生成下一个元组。
	 */
	if (fc_fsstate->next_tuple < fc_fsstate->num_tuples)
	{
		fc_produce_tuple_asynchronously(fc_areq, true);
		return;
	}

	/* 我们必须已经用完元组 */
	Assert(fc_fsstate->next_tuple >= fc_fsstate->num_tuples);

	/* 请求当前应处于进行中状态 */
	Assert(fc_fsstate->conn_state->pendingAreq == fc_areq);

	/* 出错时，报告原始查询，而不是 FETCH。 */
	if (!PQconsumeInput(fc_fsstate->conn))
		pgfdw_report_error(ERROR, NULL, fc_fsstate->conn, false, fc_fsstate->query);

	fc_fetch_more_data(fc_node);

	fc_produce_tuple_asynchronously(fc_areq, true);
}

/*
 * 异步生成来自外部 PostgreSQL 表的下一个元组。
 */
static void fc_produce_tuple_asynchronously(AsyncRequest *fc_areq, bool fc_fetch)
{
	ForeignScanState *fc_node = (ForeignScanState *) fc_areq->requestee;
	PgFdwScanState *fc_fsstate = (PgFdwScanState *) fc_node->fdw_state;
	AsyncRequest *fc_pendingAreq = fc_fsstate->conn_state->pendingAreq;
	TupleTableSlot *fc_result;

	/* 如果请求当前处于进行中状态，则不应调用此函数 */
	Assert(fc_areq != fc_pendingAreq);

	/* 如果我们已经用完元组，则获取更多元组 */
	if (fc_fsstate->next_tuple >= fc_fsstate->num_tuples)
	{
		/* 如果我们已经检测到 EOF，则没有意义再获取一次 */
		if (!fc_fsstate->eof_reached)
		{
			/* 将请求标记为待处理以进行回调 */
			ExecAsyncRequestPending(fc_areq);
			/* 开始另一个请求的提取，如果有请求并且没有待处理请求 */
			if (fc_fetch && !fc_pendingAreq)
				fc_fetch_more_data_begin(fc_areq);
		}
		else
		{
			/* 没有更多的事情可做；只需返回一个 NULL 指针 */
			fc_result = NULL;
			/* 将请求标记为完成 */
			ExecAsyncRequestDone(fc_areq, fc_result);
		}
		return;
	}

	/* 从 ForeignScan 节点获取一个元组 */
	fc_result = fc_areq->requestee->ExecProcNodeReal(fc_areq->requestee);
	if (!TupIsNull(fc_result))
	{
		/* 将请求标记为完成 */
		ExecAsyncRequestDone(fc_areq, fc_result);
		return;
	}

	/* 我们必须已经用完元组 */
	Assert(fc_fsstate->next_tuple >= fc_fsstate->num_tuples);

	/* 如果我们还没有检测到 EOF，则提取一些更多的元组 */
	if (!fc_fsstate->eof_reached)
	{
		/* 将请求标记为待处理以进行回调 */
		ExecAsyncRequestPending(fc_areq);
		/* 开始另一个请求的提取，如果有请求并且没有待处理请求 */
		if (fc_fetch && !fc_pendingAreq)
			fc_fetch_more_data_begin(fc_areq);
	}
	else
	{
		/* 没有更多的事情可做；只需返回一个 NULL 指针 */
		fc_result = NULL;
		/* 将请求标记为完成 */
		ExecAsyncRequestDone(fc_areq, fc_result);
	}
}

/*
 * 开始异步数据提取。
 *
 * 注意：该函数假设当前没有进行中的异步
 * 数据提取。
 *
 * 注意：必须调用 fetch_more_data 来提取结果。
 */
static void fc_fetch_more_data_begin(AsyncRequest *fc_areq)
{
	ForeignScanState *fc_node = (ForeignScanState *) fc_areq->requestee;
	PgFdwScanState *fc_fsstate = (PgFdwScanState *) fc_node->fdw_state;
	char		fc_sql[64];

	Assert(!fc_fsstate->conn_state->pendingAreq);

	/* 同步创建游标。 */
	if (!fc_fsstate->cursor_exists)
		fc_create_cursor(fc_node);

	/* 我们将发送此查询，但不等待响应。 */
	snprintf(fc_sql, sizeof(fc_sql), "FETCH %d FROM c%u",
			 fc_fsstate->fetch_size, fc_fsstate->cursor_number);

	if (!PQsendQuery(fc_fsstate->conn, fc_sql))
		pgfdw_report_error(ERROR, NULL, fc_fsstate->conn, false, fc_fsstate->query);

	/* 记住请求正在处理 */
	fc_fsstate->conn_state->pendingAreq = fc_areq;
}

/*
 * 处理待处理的异步请求。
 */
void process_pending_request(AsyncRequest *fc_areq)
{
	ForeignScanState *fc_node = (ForeignScanState *) fc_areq->requestee;
	PgFdwScanState *fc_fsstate = (PgFdwScanState *) fc_node->fdw_state;

	/* 请求将处于待处理状态，等待回调 */
	Assert(fc_areq->callback_pending);

	/* 请求当前应处于进行中状态 */
	Assert(fc_fsstate->conn_state->pendingAreq == fc_areq);

	fc_fetch_more_data(fc_node);

	/*
	 * 如果我们没有得到任何元组，必须是数据结束；现在完成请求。
	 * 否则，我们将推迟完成请求，直到我们被调用
	 * 从 postgresForeignAsyncConfigureWait()/postgresForeignAsyncNotify()。
	 */
	if (fc_fsstate->next_tuple >= fc_fsstate->num_tuples)
	{
		/* 与 AsyncNotify 不同，我们自己取消设置 callback_pending */
		fc_areq->callback_pending = false;
		/* 将请求标记为完成 */
		ExecAsyncRequestDone(fc_areq, NULL);
		/* 与 AsyncNotify 不同，我们自己调用 ExecAsyncResponse */
		ExecAsyncResponse(fc_areq);
	}
}

/*
 * 完成一个待处理的异步请求。
 */
static void fc_complete_pending_request(AsyncRequest *fc_areq)
{
	/* 请求将处于待处理状态，等待回调 */
	Assert(fc_areq->callback_pending);

	/* 与 AsyncNotify 不同，我们自己取消设置 callback_pending */
	fc_areq->callback_pending = false;

	/* 如果有必要，我们之后开始提取；不提取 */
	fc_produce_tuple_asynchronously(fc_areq, false);

	/* 与 AsyncNotify 不同，我们自己调用 ExecAsyncResponse */
	ExecAsyncResponse(fc_areq);

	/* 此外，如果需要，我们自己做仪表记录 */
	if (fc_areq->requestee->instrument)
		InstrUpdateTupleCount(fc_areq->requestee->instrument,
							  TupIsNull(fc_areq->result) ? 0.0 : 1.0);
}

/*
 * 从指定的 PGresult 行创建一个元组。
 *
 * rel 是外部表的本地表示，attinmeta 是
 * rel 的 tupdesc 的转换数据，retrieved_attrs 是一个
 * 存在于 PGresult 中的表列编号的整数列表。
 * fsstate 是 ForeignScan 计划节点的执行状态。
 * temp_context 是一个工作上下文，可以在每个元组之后重置。
 *
 * 注意：rel 或 fsstate 中的任意一个可以为 NULL，但不能同时为 NULL。
 * 如果我们正在处理远程连接，rel 为 NULL，fsstate 在非查询
 * 上下文中（如 ANALYZE）或正在处理非扫描查询节点时为 NULL。
 */
static HeapTuple fc_make_tuple_from_result_row(PGresult *fc_res,
						   int fc_row,
						   Relation fc_rel,
						   AttInMetadata *fc_attinmeta,
						   List *fc_retrieved_attrs,
						   ForeignScanState *fc_fsstate,
						   MemoryContext fc_temp_context)
{
	HeapTuple	fc_tuple;
	TupleDesc	fc_tupdesc;
	Datum	   *fc_values;
	bool	   *fc_nulls;
	ItemPointer fc_ctid = NULL;
	ConversionLocation fc_errpos;
	ErrorContextCallback fc_errcallback;
	MemoryContext fc_oldcontext;
	ListCell   *fc_lc;
	int			fc_j;

	Assert(fc_row < PQntuples(fc_res));

	/*
	 * 在一个临时上下文中执行以下工作，我们在每个元组之后重置它。
	 * 这不仅清理我们可以直接访问的数据，还清理 I/O 函数可能泄漏的任何杂质。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_temp_context);

	/*
	 * 获取行的元组描述符。如果提供了 rel，则使用 rel 的 tupdesc；
	 * 否则查看扫描节点的 ScanTupleSlot。
	 */
	if (fc_rel)
		fc_tupdesc = RelationGetDescr(fc_rel);
	else
	{
		Assert(fc_fsstate);
		fc_tupdesc = fc_fsstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
	}

	fc_values = (Datum *) palloc0(fc_tupdesc->natts * sizeof(Datum));
	fc_nulls = (bool *) palloc(fc_tupdesc->natts * sizeof(bool));
	/* 对于结果中不存在的任何列，初始化为 null */
	memset(fc_nulls, true, fc_tupdesc->natts * sizeof(bool));

	/*
	 * 设置并安装回调，以报告转换错误发生的位置。
	 */
	fc_errpos.cur_attno = 0;
	fc_errpos.rel = fc_rel;
	fc_errpos.fsstate = fc_fsstate;
	fc_errcallback.callback = fc_conversion_error_callback;
	fc_errcallback.arg = (void *) &fc_errpos;
	fc_errcallback.previous = error_context_stack;
	error_context_stack = &fc_errcallback;

	/*
	 * i 索引关系中的列，j 索引 PGresult 中的列。
	 */
	fc_j = 0;
	foreach(fc_lc, fc_retrieved_attrs)
	{
		int			fc_i = lfirst_int(fc_lc);
		char	   *fc_valstr;

		/* 获取下一个列的文本值 */
		if (PQgetisnull(fc_res, fc_row, fc_j))
			fc_valstr = NULL;
		else
			fc_valstr = PQgetvalue(fc_res, fc_row, fc_j);

		/*
		 * 将值转换为内部表示
		 *
		 * 注意：我们在结果中忽略除了 ctid 和 oid 以外的系统列
		 */
		fc_errpos.cur_attno = fc_i;
		if (fc_i > 0)
		{
			/* 普通列 */
			Assert(fc_i <= fc_tupdesc->natts);
			fc_nulls[fc_i - 1] = (fc_valstr == NULL);
			/* 即使是 null，也应用输入函数，以支持域 */
			fc_values[fc_i - 1] = InputFunctionCall(&fc_attinmeta->attinfuncs[fc_i - 1],
											  fc_valstr,
											  fc_attinmeta->attioparams[fc_i - 1],
											  fc_attinmeta->atttypmods[fc_i - 1]);
		}
		else if (fc_i == SelfItemPointerAttributeNumber)
		{
			/* ctid */
			if (fc_valstr != NULL)
			{
				Datum		fc_datum;

				fc_datum = DirectFunctionCall1(tidin, CStringGetDatum(fc_valstr));
				fc_ctid = (ItemPointer) DatumGetPointer(fc_datum);
			}
		}
		fc_errpos.cur_attno = 0;

		fc_j++;
	}

	/* 卸载错误上下文回调。 */
	error_context_stack = fc_errcallback.previous;

	/*
	 * 检查我们得到了期望的列数。注意：j == 0 和
	 * PQnfields == 1 是预期的，因为 deparse 如果没有列会发出 NULL。
	 */
	if (fc_j > 0 && fc_j != PQnfields(fc_res))
		elog(ERROR, "remote query result does not match the foreign table");

	/*
	 * 在调用者的内存上下文中构建结果元组。
	 */
	MemoryContextSwitchTo(fc_oldcontext);

	fc_tuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);

	/*
	 * 如果我们有 CTID 返回，安装它在 t_self 和 t_ctid 中。
	 * t_self 是正常的位置，但如果元组被转换为一个组合 Datum，
	 * t_self 将会丢失；设置 t_ctid 允许 CTID 在 EvalPlanQual 重新评估期间被
	 * 保留（请参见 ROW_MARK_COPY 代码）。
	 */
	if (fc_ctid)
		fc_tuple->t_self = fc_tuple->t_data->t_ctid = *fc_ctid;

	/*
	 * 踩在由 heap_form_tuple 创建的元组的 xmin，xmax 和 cmin 字段上。
	 * heap_form_tuple 实际上是使用 DatumTupleFields 创建元组，而不是
	 * HeapTupleFields，但执行器期望 HeapTupleFields，并会毫不犹豫地根据
	 * 该假设提取系统列。如果我们不这样做，那么例如，元组长度
	 * 最后出现在 xmin 字段中，这不是我们想要的。
	 */
	HeapTupleHeaderSetXmax(fc_tuple->t_data, InvalidTransactionId);
	HeapTupleHeaderSetXmin(fc_tuple->t_data, InvalidTransactionId);
	HeapTupleHeaderSetCmin(fc_tuple->t_data, InvalidTransactionId);

	/* 清理 */
	MemoryContextReset(fc_temp_context);

	return fc_tuple;
}

/*
 * 当在列值转换过程中发生错误时调用的回调函数。打印列和关系的名称。
 *
 * 注意，此函数不能进行任何目录查找，因为我们处于
 * 已失败的事务中。幸运的是，我们可以从关系或查询的 rangetable 获取所需的信息。
 */
static void fc_conversion_error_callback(void *fc_arg)
{
	ConversionLocation *fc_errpos = (ConversionLocation *) fc_arg;
	Relation	fc_rel = fc_errpos->rel;
	ForeignScanState *fc_fsstate = fc_errpos->fsstate;
	const char *fc_attname = NULL;
	const char *fc_relname = NULL;
	bool		fc_is_wholerow = false;

	/*
	 * 如果我们在扫描节点中，始终使用来自 rangetable 的别名，以确保
	 * 简单关系和远程连接情况之间的一致性。仅在我们不在扫描节点中时查看
	 * 关系的 tupdesc。
	 */
	if (fc_fsstate)
	{
		/* 外部扫描案例 */
		ForeignScan *fc_fsplan = castNode(ForeignScan, fc_fsstate->ss.ps.plan);
		int			fc_varno = 0;
		AttrNumber	fc_colno = 0;

		if (fc_fsplan->scan.scanrelid > 0)
		{
			/* 在对外部表的扫描中发生错误 */
			fc_varno = fc_fsplan->scan.scanrelid;
			fc_colno = fc_errpos->cur_attno;
		}
		else
		{
			/* 在对外部连接的扫描中发生错误 */
			TargetEntry *fc_tle;

			fc_tle = list_nth_node(TargetEntry, fc_fsplan->fdw_scan_tlist,
								fc_errpos->cur_attno - 1);

			/*
			 * 目标列表可以包含 Vars 和表达式。对于 Vars，我们可以
			 * 获取一些信息，但对于表达式，我们不能。因此，
			 * 对于表达式，仅显示通用上下文消息。
			 */
			if (IsA(fc_tle->expr, Var))
			{
				Var		   *fc_var = (Var *) fc_tle->expr;

				fc_varno = fc_var->varno;
				fc_colno = fc_var->varattno;
			}
		}

		if (fc_varno > 0)
		{
			EState	   *fc_estate = fc_fsstate->ss.ps.state;
			RangeTblEntry *fc_rte = exec_rt_fetch(fc_varno, fc_estate);

			fc_relname = fc_rte->eref->aliasname;

			if (fc_colno == 0)
				fc_is_wholerow = true;
			else if (fc_colno > 0 && fc_colno <= list_length(fc_rte->eref->colnames))
				fc_attname = strVal(list_nth(fc_rte->eref->colnames, fc_colno - 1));
			else if (fc_colno == SelfItemPointerAttributeNumber)
				fc_attname = "ctid";
		}
	}
	else if (fc_rel)
	{
		/* 非外部扫描案例（我们在这里应该始终有一个 rel） */
		TupleDesc	fc_tupdesc = RelationGetDescr(fc_rel);

		fc_relname = RelationGetRelationName(fc_rel);
		if (fc_errpos->cur_attno > 0 && fc_errpos->cur_attno <= fc_tupdesc->natts)
		{
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc,
												   fc_errpos->cur_attno - 1);

			fc_attname = NameStr(fc_attr->attname);
		}
		else if (fc_errpos->cur_attno == SelfItemPointerAttributeNumber)
			fc_attname = "ctid";
	}

	if (fc_relname && fc_is_wholerow)
		errcontext("whole-row reference to foreign table \"%s\"", fc_relname);
	else if (fc_relname && fc_attname)
		errcontext("column \"%s\" of foreign table \"%s\"", fc_attname, fc_relname);
	else
		errcontext("processing expression at position %d in select list",
				   fc_errpos->cur_attno);
}

/*
 * 给定一个等价类和一个外部关系，找到一个 EC 成员
 *，可以根据使用该 EC 的路径键远程对关系进行排序。
 *
 * 如果有多个合适的候选者，返回其中一个任意的
 * 候选者。如果没有，则返回 NULL。
 *
 * 这检查 EC 成员表达式仅使用给定关系中的 Vars，并且是可发送的。
 * 调用者必须单独验证路径键的排序运算符是否可发送。
 */
EquivalenceMember * find_em_for_rel(PlannerInfo *fc_root, EquivalenceClass *fc_ec, RelOptInfo *fc_rel)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_ec->ec_members)
	{
		EquivalenceMember *fc_em = (EquivalenceMember *) lfirst(fc_lc);

		/*
		 * 请注意，我们要求 !bms_is_empty，否则我们将接受不适合此目的的常量表达式。
		 */
		if (bms_is_subset(fc_em->em_relids, fc_rel->relids) &&
			!bms_is_empty(fc_em->em_relids) &&
			is_foreign_expr(fc_root, fc_rel, fc_em->em_expr))
			return fc_em;
	}

	return NULL;
}

/*
 * 在给定关系的 reltarget 中查找作为排序列计算的 EquivalenceClass 成员，并且可以移植。
 *
 * 如果有多个合适的候选者，则返回其中一个任意的。如果没有，则返回 NULL。
 *
 * 这检查 EC 成员表达式仅使用给定关系中的变量并且是可移植的。调用者必须单独验证 pathkey 的排序操作符是可移植的。
 */
EquivalenceMember * find_em_for_rel_target(PlannerInfo *fc_root, EquivalenceClass *fc_ec,
					   RelOptInfo *fc_rel)
{
	PathTarget *fc_target = fc_rel->reltarget;
	ListCell   *fc_lc1;
	int			fc_i;

	fc_i = 0;
	foreach(fc_lc1, fc_target->exprs)
	{
		Expr	   *fc_expr = (Expr *) lfirst(fc_lc1);
		Index		fc_sgref = get_pathtarget_sortgroupref(fc_target, fc_i);
		ListCell   *fc_lc2;

		/* 忽略非排序表达式 */
		if (fc_sgref == 0 ||
			get_sortgroupref_clause_noerr(fc_sgref,
										  fc_root->parse->sortClause) == NULL)
		{
			fc_i++;
			continue;
		}

		/* 我们忽略两端的二进制兼容重标记 */
		while (fc_expr && IsA(fc_expr, RelabelType))
			fc_expr = ((RelabelType *) fc_expr)->arg;

		/* 定位匹配此表达式的 EquivalenceClass 成员（如果有） */
		foreach(fc_lc2, fc_ec->ec_members)
		{
			EquivalenceMember *fc_em = (EquivalenceMember *) lfirst(fc_lc2);
			Expr	   *fc_em_expr;

			/* 不匹配常量 */
			if (fc_em->em_is_const)
				continue;

			/* 忽略子成员 */
			if (fc_em->em_is_child)
				continue;

			/* 如果相同表达式（去掉重标后）则匹配 */
			fc_em_expr = fc_em->em_expr;
			while (fc_em_expr && IsA(fc_em_expr, RelabelType))
				fc_em_expr = ((RelabelType *) fc_em_expr)->arg;

			if (!equal(fc_em_expr, fc_expr))
				continue;

			/* 检查表达式（包括重标记！）是否可移植 */
			if (is_foreign_expr(fc_root, fc_rel, fc_em->em_expr))
				return fc_em;
		}

		fc_i++;
	}

	return NULL;
}

/*
 * 确定给定外部表的批处理大小。为表指定的选项具有优先权。
 */
static int fc_get_batch_size_option(Relation fc_rel)
{
	Oid			fc_foreigntableid = RelationGetRelid(fc_rel);
	ForeignTable *fc_table;
	ForeignServer *fc_server;
	List	   *fc_options;
	ListCell   *fc_lc;

	/* 默认使用 1，这意味着“没有批处理” */
	int			fc_batch_size = 1;

	/*
	 * 加载表和服务器的选项。我们在表选项后附加服务器选项，因为表选项具有优先权。
	 */
	fc_table = GetForeignTable(fc_foreigntableid);
	fc_server = GetForeignServer(fc_table->serverid);

	fc_options = NIL;
	fc_options = list_concat(fc_options, fc_table->options);
	fc_options = list_concat(fc_options, fc_server->options);

	/* 查看表或服务器是否指定了 batch_size。 */
	foreach(fc_lc, fc_options)
	{
		DefElem    *fc_def = (DefElem *) lfirst(fc_lc);

		if (strcmp(fc_def->defname, "batch_size") == 0)
		{
			(void) parse_int(defGetString(fc_def), &fc_batch_size, 0, NULL);
			break;
		}
	}

	return fc_batch_size;
}
